Skip to content

Advanced

Middleware

Middleware is helping mechanism for detecting update events before and after they are finished.

It's similar to update function, but instead of listening to one store instance, it listens to every single store instance at the same time. Every action will trigger middleware.

class CounterStore extends Exome {
  public count = 0
  public add(count: number) {
    this.count += count
  }
}
 
addMiddleware((instance, action, payload) => {
  console.log("start", action)
  return (error) => {
    console.log("end", action)
  }
});
 
log: "start NEW"
const counterStore = new CounterStore() counterStore.add(5)
log: "start add"
log: "end add"

If current actions are not enough, runMiddleware can trigger custom middleware actions.

log: "start CUSTOM_ACTION"
const after = runMiddleware(counterStore, "CUSTOM_ACTION", []) // do something in between
log: "end CUSTOM_ACTION"

Note that middleware will NOT trigger instance update events itself since it acts on them. But in case this is needed, you can use update function to update specific instance.

import { update } from "exome"
 
addMiddleware((instance, action, payload) => {
  if (action === "CUSTOM_ACTION") {
    update(instance)
  }
})

This brings us right into next topic.

Update

Every time action is called for specific instance, it triggers update event for it automatically. But sometimes when performance is on the line and updating UI so much may not be a good idea. So there is a way to bypass actions.

It is possible to do update events manually for each instance separately.

import { Exome, subscribe, update } from "exome"
 
class PositionStore extends Exome {
  public x = 10
  public y = 10
}
 
const positionStore = new PositionStore()
 
subscribe(positionStore, console.log)
 
positionStore.x = 15
positionStore.x = 20
 
log: PositionStore { x: 20; y: 10 }

This way control is given to you when update events are triggered.

But sometimes updating one instance is not enough, in those cases you can use updateAll function to trigger update event to EVERY store instance.

import { updateAll } from "exome"
 
log: PositionStore { x: 20; y: 10 }

Save & Load

Since exome is built around class instances being used as state objects, its not like we could use JSON.stringify to save state to file or send to server.

For this reason there are 2 helper functions to help with this.

saveState

This function expects store instance to be provided. And in return it gives you string that can be saved to wherever you wish freezing current state in that string.

import { saveState } from "exome/state"
 
const counterStore = new CounterStore()
 
{"$exome_id":"CounterStore-LS5WUJPF17SF","count":5}

loadState

Since we're able to save state, it only makes sense that we could also load it back.

For this reason we can use loadState and prepare instance data will be loaded to.

import { loadState } from "exome/state";
 
CounterStore { count: 5, increment: [Function] }
const counterStore = new CounterStore() const savedStore = `{"$exome_id":"CounterStore-LS5WUJPF17SF","count":999}`
CounterStore { count: 999, increment: [Function] }

loadState function will trigger LOAD_STATE action in middlewares for specific instances that are loaded from string.

One caveat for this to properly work is that we need to register stores that are saved so that loadState knows that stores to build from:

import { registerLoadable } from "exome/state";
 
registerLoadable({
  CounterStore,
})

Instance id

Each store instance is assigned a unique id that can be accessed via getExomeId function.

import { Exome, getExomeId } from "exome"
 
class UserStore extends Exome {
  constructor(
    public name: string
  ) {
    super()
  }
 
  public rename(name: string) {
    this.name = name
  }
}
 
const userStore = new UserStore()
 
"UserStore-LS5WUJPF17SF"

This can be very useful for identifying multiple instances from the same store. This can also be used as key in react element list.

class UserListStore extends Exome {
  constructor(
    public users: UserStore[] = []
  ) {
    super()
  }
}
 
const userListStore = new UserListStore([
  new UserStore("John"),
  new UserStore("Jane"),
])
 
function App() {
  const { users } = useStore(userListStore)
 
  return (
    <ul>
      {users.map((userStore) => (
        <li key={getExomeId(userStore)}>
          {userStore.name}
        </li>
      ))}
    </ul>
  )
}

For more control over this id you can use setExomeId function to set the id yourself if you need it.

import { setExomeId } from "exome"
 
const userStore = new UserStore("John")
 
"UserStore-LS5WUJPF17SF"
getExomeId(userStore) setExomeId(userStore, "CUSTOM")
"UserStore-CUSTOM"