MobX-state-tree

MobX-state-tree

  • Documentation
  • TypeDocs
  • Sponsor
  • GitHub

›Advanced Concepts

Introduction

  • Welcome to MobX-State-Tree!
  • Installation
  • Getting Started Tutorial
  • Examples
  • Overview & Philosophy

Basic Concepts

  • Types, models, trees & state
  • Actions
  • Derived values
  • React and MST
  • Snapshots
  • Identifiers and references
  • Asynchronous actions

Advanced Concepts

  • Patches
  • Listening to changes
  • Dependency Injection
  • Middleware
  • Reconciliation
  • Volatile state

API Overview

  • Types overview
  • API overview
  • Lifecycle hooks overview

Tips

  • Talks & Blogs
  • Frequently Asked Questions
  • TypeScript and MST
  • Circular dependencies
  • Simulating inheritance
  • Using snapshots as values
  • Miscellaneous Tips

Compare

  • React Context vs. MobX-State-Tree

Recipes

  • Auto-Generated Property Setter Actions
  • Pre-built Form Types with MST Form Type
  • Manage Asynchronous Data with mst-query
Edit

Middleware

Middlewares can be used to intercept any action on a subtree.

It is allowed to attach multiple middlewares to a node. The order in which middlewares are invoked is inside-out: This means that the middlewares are invoked in the order you attach them. The value returned by the action invoked/ the aborted value gets passed through the middleware chain and can be manipulated.

The community has created a small set of pre-built / example middlewares.

Play around with a simple example of middleware in action with this CodeSandbox.

Custom Middleware

Middlewares can be attached by using:

addMiddleware(target: IAnyStateTreeNode, handler: IMiddlewareHandler, includeHooks: boolean = true) : IDisposer

target

the middleware will only be attached to actions of the target and further sub nodes of such.

handler

An example of this is as follows:

const store = SomeStore.create()
const disposer = addMiddleware(store, (call, next, abort) => {
    console.log(`action ${call.name} was invoked`)
    // runs the next middleware
    // or the implementation of the targeted action
    // if there is no middleware left to run

    // the value returned from the next can be manipulated
    next(call, (value) => value + 1)
})
const store = SomeStore.create()
const disposer = addMiddleware(store, (call, next, abort) => {
    console.log(`action ${call.name} was invoked`)
    // aborts running the middlewares and returns the 'value' instead.
    // note that the targeted action won't be reached either.
    return abort("value")
})

A middleware handler receives three arguments:

  1. the description of the the call,
  • a function to invoke the next middleware in the chain and manipulate the returned value from the next middleware in the chain.
  • a function to abort the middleware queue and return a value.

Note: You must call either next(call) or abort(value) within a middleware.

Note: If you abort, the action invoked will never be reached.

Note: The value from either abort('value') or the returned value from the action can be manipulated by previous middlewares.

Note: It is important to invoke next(call) or abort(value) synchronously.

Note: The value of the abort(value) must be a promise in case of aborting a flow.

call

export type IMiddleWareEvent = {
    type: IMiddlewareEventType
    name: string
    id: number
    parentId: number
    rootId: number
    allParentIds: number[]
    tree: IStateTreeNode
    context: IStateTreeNode
    args: any[]
}

export type IMiddlewareEventType =
    | "action"
    | "flow_spawn"
    | "flow_resume"
    | "flow_resume_error"
    | "flow_return"
    | "flow_throw"
  • name is the name of the action
  • context is the object on which the action was defined & invoked
  • tree is the root of the MST tree in which the action was fired (tree === getRoot(context))
  • args are the original arguments passed to the action
  • id is a number that is unique per external action invocation.
  • parentId is the number of the action / process that called this action. 0 if it wasn't called by another action but directly from user code
  • rootid is the id of the action that spawned this action. If an action was not spawned by another action, but something external (user event etc), id and rootId will be equal (and parentid 0)
  • allParentIds is the chain from root until current (excluding current) that called this action. [] if it wasn't called by another action but directly from user code

type Indicates which kind of event this is

  • action: this is a normal synchronous action invocation
  • flow_spawn: The invocation / kickoff of a process block (see asynchronous actions)
  • flow_resume: a promise that was returned from yield earlier has resolved. args contains the value it resolved to, and the action will now continue with that value
  • flow_resume_error: a promise that was returned from yield earlier was rejected. args contains the rejection reason, and the action will now continue throwing that error into the generator
  • flow_return: the generator completed successfully. The promise returned by the action will resolve with the value found in args
  • flow_throw: the generator threw an uncatched exception. The promise returned by the action will reject with the exception found in args

To see how a bunch of calls from an asynchronous process look, see the unit tests

A minimal, empty process will fire the following events if started as action:

  1. action: An action event will always be emitted if a process is exposed as action on a model)
  2. flow_spawn: This is just the notification that a new generator was started
  3. flow_resume: This will be emitted when the first "code block" is entered. (So, with zero yields there is one flow_resume still)
  4. flow_return: The process has completed

next

use next to call the next middleware.

next(call: IMiddlewareEvent, callback?: (value: any) => any): void

  • call Before passing the call middleware, feel free to (clone and) modify the call.args. Other properties should not be modified

  • callback can be used to manipulate values returned by later middlewares or the implementation of the targeted action.

abort

use abort if you want to kill the queue of middlewares and immediately return. the implementation of the targeted action won't be reached if you abort the queue.

abort(value: any) : void

  • value is returned instead of the return value from the implementation of the targeted action.

includeHooks

set this flag to false if you want to avoid having hooks passed to the middleware.

FAQ

  • I alter a property and the change does not appear in the middleware.

  • If you alter a value of an unprotected node, the change won't reach the middleware. Only actions can be intercepted.

← Dependency InjectionReconciliation →
  • Custom Middleware
    • target
    • handler
    • includeHooks
  • FAQ
MobX-state-tree
Docs
Getting StartedBasic conceptsAdvanced conceptsAPI Reference
Community
GitHub DiscussionsStack Overflow
More
BlogGitHubStar
Facebook Open Source
Copyright © 2025 Michel Weststrate