Basics
Stores
Store can be a single class or multiple ones. It's suggested keeping stores small, in terms of property sizes. Any piece of store you have, must use a class that extends Exome
.
import { Exome } from "exome"
class TodoStore extends Exome {
}
Any store can be initialized (multiple times if needed) becoming an instance of a store.
const todoStore = new TodoStore()
Listen to changes
There are multiple ways to listen to changes in store, but one default method is subscribing to store instance using subscribe
method.
subscribe(todoStore, () => {
console.log("todoStore had changes")
})
Properties
Remember that this is quite a regular class (with some behind the scenes work). So you can write you data inside properties however you like. Properties can be public, private, object, arrays, getters, setters, static etc. It's all just a value.
import { Exome } from "exome"
class TodoStore extends Exome {
public todo: { text: string, done: boolean }[] = []
}
Properties can also be defined inside constructor, just like any regular class:
import { Exome } from "exome"
class TodoStore extends Exome {
constructor(
public todo: { text: string, done: boolean }[] = []
) {
super()
}
}
For dynamic/computed properties you can easily use getters:
import { Exome } from "exome"
class TodoStore extends Exome {
public todo: { text: string, done: boolean }[] = []
public get countDone() {
return this.todo.filter(({ done }) => done).length
}
}
Actions
Every method in class is considered as an action. They should be only used for changing store instance properties. Whenever any method is called in Exome it triggers update to middleware (e.g. updates UI components). Actions can be regular sync methods and even async ones.
import { Exome } from "exome"
class TodoStore extends Exome {
public todo: { text: string, done: boolean }[] = []
public addTodo(todo: TodoStore["todo"][number]) {
this.todo.push(todo)
}
}
If you want to get something from state via method, use getters or properties as functions (those will not be tracked for changes).
import { Exome } from "exome"
class TodoStore extends Exome {
public todo: { text: string, done: boolean }[] = []
`getTodo` is NOT an action if arrow function is used public getTodo = (index: number) => {
return this.todo[index]
}
}
getTodo
is not an action since it's defined as a property. This can be useful to get data by passing some argument. Since getting data doesn't need to trigger updates, no need to define it as action.
Async Actions
Async actions are async methods. These are methods that return Promise. Update for instance is issued only after this Promise is fulfilled or rejected.
import { Exome } from "exome"
class TodoStore extends Exome {
public todo: { text: string, done: boolean }[] = []
public async fetchTodoList() {
this.todo = await fetch(`data:,[{"text":"Wash dishes",done:true}]`)
.then((res) => res.json())
}
}
Getting Action Status
Every action can have a special utility applied. This especially useful for async actions as the utility getActionStatus
provides loading and error state for particular actions.
import { Exome } from "exome"
import { getActionStatus } from "exome/utils"
class TodoStore extends Exome {
public todo: { text: string, done: boolean }[] = []
public get status() {
return getActionStatus(this, "fetchTodoList");
}
public async fetchTodoList() {
this.todo = await fetch(`data:,[{"text":"Wash dishes",done:true}]`)
.then((res) => res.json())
}
}
Use it in a getter as it returns cached object with this interface:
interface ActionStatus<E = Error> {
loading: boolean
error: false | E
unsubscribe: () => void
}
loading
- if action is in progress;error
- if and what did the action throw;unsubscribe
- stop listening to this status.
Effects
Effects are a great way to do something extra based on actions called. For example to implement logging. Or sending & receiving state from and to server.
import { onAction } from "exome"
onAction(TodoStore, "addTodo", (instance, action, args) => {
console.log("New item:", args[0].text)
console.log("Item count:", instance.todo.length)
})
const todoStore = new TodoStore()
todoStore.addTodo({ text: "Workout", done: false })
log: "New item: Workout" log: "Item count: 1"
By default all onAction
callbacks are being called after action is finished. But it's possible to trigger callback before action is triggered too.
onAction(TodoStore, "addTodo", (instance, action, args) => {
console.log("New item:", args[0].text)
console.log("Item count:", instance.todo.length)
}, "before")
const todoStore = new TodoStore()
todoStore.addTodo({ text: "Workout", done: false })
log: "New item: Workout" log: "Item count: 0"
Get result from action
Effects can also detect if action failed or succeeded and with what response.
import { onAction } from "exome"
onAction(TodoStore, "addTodo", (
instance,
action,
args,
error,
response,
) => {
if (error) {
// action did throw error
}
console.log(response) // action returned something
})
This is only possible when onAction
is set to receive event after
action is triggered (that is the default behaviour).
If actions are async, then response and throw returns awaited response, not promise.