MobX is a simple, scalable, time-tested solution for state management.

This tutorial will walk you through all the important MobX concepts in 10 minutes. MobX is a stand-alone library, but most people use it in conjunction with React, so this tutorial will focus on their use in combination.

The core ideas

State is at the heart of all applications, and there is no better way to create buggy, unmanageable applications than to “create unstable states or create states that are out of sync with surrounding local variables.”

Therefore, many State management solutions attempt to restrict the ways in which State can be changed, such as by making them immutable.

But this introduces new problems: data needs to be normalized, referential integrity cannot be guaranteed, and powerful concepts like prototypes are almost impossible to use.

MobX resimplifies State management by addressing the root problem: we simply cannot create unstable states.

The strategy for achieving this goal is simple: ensure that everything derived from application state can be derived automatically.

In principle, MobX treats your app like a spreadsheet:

  1. First of all, let’s seeApplication State. Objects, arrays, stereotypes, and references make up your application’s Model.
  2. Second, lookDerivations. Logically, all values that can be calculated automatically with the application state are derived. These derived or calculated values range from simple values, such as the number of toDos not completed, to complex values, such as a visual HTML representing a TODO. From a spreadsheet perspective: These are formulas and charts for the application.
  3. Keep your hands still and make eye contactIt’s very similar to the derivation. The main difference is that these functions do not generate values, but instead perform tasks automatically, usually related to I/O. They ensure that DOM updates or network requests are made automatically at the right time.
  4. And finally let’s seeActions. Action is all changestateThing to do. MobX will ensure that all state changes triggered by your actions are handled by all derivations and responses. The process is synchronous and trouble-free.

A simple Todo Store

Once the theory is out of the way, it’s probably better to try it out than to read it carefully. To be creative, let’s start with a very simple Todo Store. Note: All of the code blocks below are editable and can be executed by clicking the Run Code button. Please refer to the original article for detailed execution results. Here is a very simple TodoStore for managing to-do lists. I haven’t joined MobX yet.

class TodoStore {
    todos = [];

    get completedTodosCount() {
        return this.todos.filter(
            todo => todo.completed === true
        ).length;
    }

    report() {
        if (this.todos.length === 0)
            return "<none>";
        return `Next todo: "${this.todos[0].task}". ` +
            `Progress: ${this.completedTodosCount}/${this.todos.length}`;
    }

    addTodo(task) {
        this.todos.push({
            task: task,
            completed: false,
            assignee: null
        });
    }
}

const todoStore = new TodoStore();
Copy the code

We just created a todoStore instance that contains a to-do list. It’s time to fill it with some objects. To ensure that we can see the impact of our changes, we call todoStore.report after each change and print it. Note that this report deliberately prints only the first task. This makes this example a little awkward, but you’ll see that it’s a good example of how MobX’s dependency tracing is dynamic.

todoStore.addTodo("read MobX tutorial");
console.log(todoStore.report());

todoStore.addTodo("try MobX");
console.log(todoStore.report());

todoStore.todos[0].completed = true;
console.log(todoStore.report());

todoStore.todos[1].task = "try MobX in own project";
console.log(todoStore.report());

todoStore.todos[0].task = "grok MobX tutorial";
console.log(todoStore.report());
Copy the code

Becoming reactive

So far, this code is nothing special. But what if instead of calling report explicitly, we want life to be called every time the state changes? This will eliminate the need to call the report in all the places that might affect it. We want to ensure that the latest reports are printed. But we don’t want to worry about how to organize it.

Thankfully, that’s exactly what MobX can do for you. Automatic execution of completely state-dependent code. So our report function updates automatically like a chart in a spreadsheet. To achieve this, TodoStore needs to become an Observable so MobX can track all changes. Let’s change the code to do it.

Further, the completedTodosCount attribute can be automatically derived from the Todo List. We can add observable properties to an object using @Observable and @computed decorators:

class ObservableTodoStore {
    @observable todos = [];
    @observable pendingRequests = 0;

    constructor() {
        mobx.autorun(() => console.log(this.report));
    }

    @computed get completedTodosCount() {
        return this.todos.filter(
            todo => todo.completed === true
        ).length;
    }

    @computed get report() {
        if (this.todos.length === 0)
            return "<none>";
        return `Next todo: "${this.todos[0].task}". ` +
            `Progress: ${this.completedTodosCount}/${this.todos.length}`;
    }

    addTodo(task) {
        this.todos.push({
            task: task,
            completed: false,
            assignee: null
        });
    }
}


const observableTodoStore = new ObservableTodoStore();
Copy the code

Done! We’ve tagged MobX with @Observable properties whose values can change at any time. The values are computed using @computed notation to indicate that they can be derived by state.

The pendingRequests and assignee attributes are not currently used, but will be later in this tutorial. For brevity, the examples on this page use ES6, JSX, and decorators. But don’t worry, all decorators in MobX should be in ES5 form.

In the constructor, we create a small function to print the report and wrap it with Autorun. Autorun creates a Reaction and executes it once, then automatically executes the response whenever any Observable data changes in the function. Since the Report uses the Observable Todos property, it prints the report at all appropriate times. The following example illustrates this point by clicking the Run button. :

observableTodoStore.addTodo("read MobX tutorial");
observableTodoStore.addTodo("try MobX");
observableTodoStore.todos[0].completed = true;
observableTodoStore.todos[1].task = "try MobX in own project";
observableTodoStore.todos[0].task = "grok MobX tutorial";
Copy the code

It’s fun, isn’t it? The report is printed automatically, and the process is automatic with no intermediate variable leakage. If you look closely at the log, you will see that the fourth line generates no new log lines. Because the report doesn’t really change because of the rename, although the underlying data does change. Changing the name of the first todo changes the report because its name is used by the report. This is a good indication that Autorun listens not only on the TODO array, but also on individual attributes in the TODO element.

React makes React reactive

Ok, so far we have created a simple responsive report. It’s time to build a responsive user interface around the store. The React component cannot React to the outside world (except its own name). The @Observer decorator for the Mobx-React package solves this problem by wrapping the Render method of the React component in autorun, which automatically keeps your components and state in sync. In theory, this is no different from what we did with the report.

The following example defines some React components. Only @Observer of these components belongs to MobX. However, it is sufficient to ensure that all components can be independently rerendered when the associated data changes. You no longer need to call setState, nor do you have to worry about how to subscribe to the appropriate portion of application state by configuring selectors or higher-order components. So to speak, all components become intelligent. But they are defined in terms of silly statements.

Click the Run Code button to see the results of the code below. This example is editable, so you can play around with it. Try deleting all @Obervers or just the one that decorates the TodoView. The numbers in the preview on the right are highlighted each time the component is re-rendered.

@observer class TodoList extends React.Component { render() { const store = this.props.store; return ( <div> { store.report } <ul> { store.todos.map( (todo, idx) => <TodoView todo={ todo } key={ idx } /> ) } </ul> { store.pendingRequests > 0 ? <marquee>Loading... </marquee> : null } <button onClick={ this.onNewTodo }>New Todo</button> <small> (double-click a todo to edit)</small> <RenderCounter  /> </div> ); } onNewTodo = () => { this.props.store.addTodo(prompt('Enter a new todo:','coffee plz')); } } @observer class TodoView extends React.Component { render() { const todo = this.props.todo; return ( <li onDoubleClick={ this.onRename }> <input type='checkbox' checked={ todo.completed } onChange={ this.onToggleCompleted } /> { todo.task } { todo.assignee ? <small>{ todo.assignee.name }</small> : null } <RenderCounter /> </li> ); } onToggleCompleted = () => { const todo = this.props.todo; todo.completed = ! todo.completed; } onRename = () => { const todo = this.props.todo; todo.task = prompt('Task name', todo.task) || todo.task; } } ReactDOM.render( <TodoList store={ observableTodoStore } />, document.getElementById('reactjs-app') );Copy the code

This next example is a perfect example of how we can change our data without doing anything else. MobX will automatically derive and update the user interface related parts from the Store state.

const store = observableTodoStore; store.todos[0].completed = ! store.todos[0].completed; store.todos[1].task = "Random todo " + Math.random(); store.todos.push({ task: "Find a fine cheese", completed: true }); // etc etc.. add your own statements here...Copy the code

Using a reference

So far, we’ve created Observables (both archetypes and normal objects), arrays, and primitives. You may wonder, how does MobX handle these references? Can our state be used to create a chart? In the example above, you might find an assignee attribute on todo. Let’s give them some values and assign tasks to them by introducing another “store” that contains people’s information (well, it’s just a glorified array).

var peopleStore = mobx.observable([
    { name: "Michel" },
    { name: "Me" }
]);
observableTodoStore.todos[0].assignee = peopleStore[0];
observableTodoStore.todos[1].assignee = peopleStore[1];
peopleStore[0].name = "Michel Weststrate";
Copy the code

We now have two separate stores. One contains personnel information and the other contains TODO information. To assign an assignee to a person in the staff store, we only need to add a reference. These changes are automatically picked up by TodoView. With MobX, we don’t need to format data and write selectors to ensure that our components can be updated. In fact, it doesn’t even matter where the data is stored. MobX will track objects as long as they are set to Obervable. A real JavaScript reference will work. If they are related to a derivation, MobX will automatically track them. To test this, just try changing the name in the input box below (make sure you hit the Run Code button before testing!). .

Asynchronous operations

Since all the data in our Todo applet is derived from State, it doesn’t matter when state changes. This makes it surprisingly easy to create asynchronous operations. Click the button below (multiple times) to simulate creating a new to-do item asynchronously.



The following code is very simple. We started by updating the pendingRequests store property to make the UI show the current load status. When the load is complete, Vaughn keeps track of the backlog items in the new store and reduces the pendingRequests count again. Compare this code to the TodoList definition above to learn how to use the pendingRequests attribute.

observableTodoStore.pendingRequests++;
setTimeout(function() {
    observableTodoStore.addTodo('Random Todo ' + Math.random());
    observableTodoStore.pendingRequests--;
}, 2000);
Copy the code

The development tools

The mobx-React-devtools package provides a development tool for mobx + ReactJS applications displayed in the upper right corner of the screen. Clicking the first button will highlight each @Observer component that has been rerendered. If you click the second button, the component dependency tree in the preview will be displayed, and you can detect exactly what piece of data it is looking at at any time.

conclusion

That’s all! There is no sample. There are just a few simple declarative components that form our overall UI. This UI is derived fully and responsively from our state. You can now start using mobx and Mobx-React packages in your applications. Here’s a quick summary of what you’ve learned so far:

  1. use@observableA decorator orobservable(object or array)Function allows MobX to track objects.
  2. @computedDecorators can be used to create functions that automatically calculate values based on state.
  3. useautorunAutomatically run observable state-dependent functions. This is useful for logging, making network requests, etc.
  4. usemobx-reactIn the package@observerThe decorator makes your React component truly responsive. They will be updated automatically and efficiently. Even in large, complex projects with large amounts of data.

Spend some time playing with the editable code block above to get a basic idea of how MobX responds to your actions. For example, you can add a log statement to the report function to see when it is called; Or don’t show the report at all to see what happens to TodoList’s rendering; Or in some cases don’t show it…

MobX is not a state container

MobX is often referred to as an alternative to Redux. Note, however, that MobX is just a library for solving technical problems and does not have a state container of its own. In this sense, the examples above are contriving, so we recommend that you use appropriate engineering practices, such as encapsulating logic in methods, organizing it in stores or controllers, and so on. Or, as one user in HackerNews put it:

“MobX, it’s always mentioned, but I can’t help but praise it. Writing with MobX means that it can do all the controllers/dispatchers/actions/supervisors and other things you need to think about to manage the flow of data, Not just doing what a Todo app does by default.”