Skip to main content

onActionMiddleware

This middleware observes actions in a given tree. It invokes listeners only for top-level actions, not for nested child actions or intermediate flow steps. Life-cycle hooks such as onAttachedToRootStore and its returned disposer are also excluded.

Its main use is to keep track of top-level actions that can be later replicated via applyAction somewhere else (another machine, etc.).

There are two kinds of possible listeners, onStart and onFinish listeners.

  • onStart listeners are called before the action executes and allow cancellation by returning a new result (either a return or a throw).
  • onFinish listeners are called after the action executes, have access to the action's actual return value and allow overriding by returning a new return value (which might be a return or a throw).

The ActionCall passed to a listener is not directly serializable. To store or send it, encode it with a serializer (e.g. defaultSerializer.encodeCall(root, actionCall)), and run it on the other side with decodeCall and applyCall / applyCallAsync, as shown in the Client/Server Example. Encode it in onStart, before the action runs, so its arguments are encoded against the tree the receiver has.

It will return a disposer, which only needs to be called if you plan to early dispose of the middleware.

const disposer = onActionMiddleware(myTodoList, {
onStart(actionCall, actionContext) {
// we could encode the action call as JSON and do something with it
const encodedCall = defaultSerializer.encodeCall(getRoot(myTodoList), actionCall)

// optionally cancel the action by throwing something
return {
result: ActionTrackingResult.Throw,
value: new Error("whatever"),
}

// or by returning a different value
// return {
// result: ActionTrackingResult.Return,
// value: 42,
// }

// or do nothing / return `undefined` to continue it
},

onFinish(actionCall, actionContext, ret) {
if (ret.result === ActionTrackingResult.Return) {
// the action succeeded and `ret.value` has the return value
} else if (ret.result === ActionTrackingResult.Throw) {
// the action threw and `ret.value` has the thrown value
}

// as in above, we can either return an object with what to return / throw
// or do nothing / return `undefined` to continue the action
},
})