Actions

By default, nodes can only be modified by one of their actions, or by actions higher up in the tree. Actions can be defined by returning an object from the action initializer function that was passed to actions. The initializer function is executed for each instance, so that self is always bound to the current instance. Also, the closure of that function can be used to store so called volatile state for the instance, or to create private functions that can only be invoked from the actions, but not from the outside.

const Todo = types.model({
        title: types.string
    })
    .actions(self => {
        function setTitle(newTitle) {
            self.title = newTitle
        }

        return {
            setTitle
        }
    })

Or, shorter, if no local state or private functions are involved:

const Todo = types.model({
        title: types.string
    })
    .actions(self => ({ // note the `({`, we are returning an object literal
        setTitle(newTitle) {
            self.title = newTitle
        }
    }))

Actions are replayable and are therefore constrained in several ways:

  • Trying to modify a node without using an action will throw an exception.
  • It's recommended to make sure action arguments are serializable. Some arguments can be serialized automatically, such as relative paths to other nodes.
  • Actions can only modify models that belong to the (sub)tree on which they are invoked.
  • You cannot use this inside actions. Instead, use self. This makes it safe to pass actions around without binding them or wrapping them in arrow functions.

Useful Methods

  • onAction: listens to any action that is invoked on the model or any of its descendants.
  • addMiddleware: listens to any action that is invoked on the model or any of its descendants.
  • applyAction: invokes an action on the model according to the given action description.

Asynchronous Actions

Asynchronous actions have first-class support in MST. Asynchronous actions are written by using generators and always return a promise.

import { types, flow } from "mobx-state-tree"

someModel.actions(self => {
    const fetchProjects = flow(function* () { // <- note the star, this a generator function!
        self.state = "pending"
        try {
            // ... yield can be used in async/await style
            self.githubProjects = yield fetchGithubProjectsSomehow()
            self.state = "done"
        } catch (e) {
            // ... including try/catch error handling
            console.error("Failed to fetch projects", error)
            self.state = "error"
        }
        // The action will return a promise that resolves to the returned value
        // (or rejects with anything thrown from the action)
        return self.githubProjects.length
    })

    return { fetchProjects }
})

Action Listeners versus Middleware

The difference between action listeners and middleware is: Middleware can intercept the action that is about to be invoked, modify arguments, return types, etc. Action listeners cannot intercept, and are only notified. Action listeners receive the action arguments in a serializable format, while middleware receives the raw arguments. (onAction is actually just a built-in middleware.)

Disabling Protected Mode

This may be desired if the default protection of mobx-state-tree doesn't fit your use case. For example, if you are not interested in replayable actions, or hate the effort of writing actions to modify any field, unprotect(tree) will disable the protected mode of a tree, allowing anyone to directly modify the tree.

results matching ""

    No results matching ""