MobX is a simple, scalable and battle tested state management solution. This tutorial will teach you all the important concepts of MobX in ten minutes. MobX is a standalone library, but most people are using it with React and this tutorial focuses on that combination.
The core idea
State is the heart of each application and there is no quicker way to create buggy, unmanageable applications than by producing an inconsistent state or state that is out-of-sync with local variables that linger around. Hence many state management solutions try to restrict the ways in which you can modify state, for example by making state immutable. But this introduces new problems; data needs to be normalized, referential integrity can no longer be guaranteed and it becomes next to impossible to use powerful concepts like prototypes.
MobX makes state management simple again by addressing the root issue: it makes it impossible to produce an inconsistent state. The strategy to achieve that is simple: Make sure that everything that can be derived from the application state, will be derived. Automatically.
Conceptually MobX treats your application like a spreadsheet.
- First of all, there is the application state. Graphs of objects, arrays, primitives, references that forms the model of your application. These values are the “data cells” of your application.
- Secondly there are derivations. Basically, any value that can be computed automatically from the state of your application. These derivations, or computed values, can range from simple values, like the number of unfinished todos, to complex stuff like a visual HTML representation of your todos. In spreadsheet terms: these are the formulas and charts of your application.
- Reactionsare very similar to derivations. The main difference is these functions don’t produce a value. Instead, they run automatically to perform some task. Usually this is I/O related. They make sure that the DOM is updated or that network requests are made automatically at the right time.
- Finally there are actions. Actions are all the things that alter the state. MobX will make sure that all changes to the application state caused by your actions are automatically processed by all derivations and reactions. Synchronously and glitch-free.
A simple todo store…
Enough theory, seeing it in action probably explains more than carefully reading the above stuff. For originality’s sake let’s start with a very simple ToDo store. Note that all the code blocks below are editable, So use the run code buttons to execute them. Below is a very straightforward TodoStore
that maintains a collection of todos. No MobX involved yet.
class TodoStore {Copy the code
todos = [];Copy the code
Copy the code
get completedTodosCount() {Copy the code
return this.todos.filter(Copy the code
todo => todo.completed === trueCopy the code
).length;Copy the code
}Copy the code
Copy the code
report() {Copy the code
if (this.todos.length === 0)Copy the code
return "<none>";Copy the code
return `Next todo: "${this.todos[0].task}". ` +Copy the code
`Progress: ${this.completedTodosCount}/${this.todos.length}`;Copy the code
}Copy the code
Copy the code
addTodo(task) {Copy the code
this.todos.push({Copy the code
task: task,Copy the code
completed: false,Copy the code
assignee: nullCopy the code
});Copy the code
}Copy the code
}Copy the code
Copy the code
const todoStore = new TodoStore();Copy the code
Copy the code
We just created a todoStore instance with a todos collection. Time to fill the todoStore with some objects. To make sure we see the effects of our changes we invoke todoStore.report after each change and log it. Note that the report intentionally always prints the first task only. It makes this example a bit artificial, but as you will see below it nicely demonstrates that MobX’s dependency tracking is dynamic.
todoStore.addTodo("read MobX tutorial");Copy the code
console.log(todoStore.report());Copy the code
Copy the code
todoStore.addTodo("try MobX");Copy the code
console.log(todoStore.report());Copy the code
Copy the code
todoStore.todos[0].completed = true;Copy the code
console.log(todoStore.report());Copy the code
Copy the code
todoStore.todos[1].task = "try MobX in own project";Copy the code
console.log(todoStore.report());Copy the code
Copy the code
todoStore.todos[0].task = "grok MobX tutorial";Copy the code
console.log(todoStore.report());Copy the code
Copy the code
Becoming reactive
So far, there is nothing special about this code. But what if we didn’t have to call report
explicitly, but could just declare that we want it to be invoked upon each state change? That would free us from the responsibility of calling report
from any place in our code base that might affect the report. We want to be sure the latest report is printed. But we don’t wanna be bothered by organizing that.
Luckily that is exactly what MobX can do for you. Automatically execute code that solely depends on state. So that our report function updates automatically, just like a chart in a spreadsheet. To achieve that, the TodoStore has to become observable so that MobX can track all the changes that are being made. Let’s alter the class just enough to achieve that.
Also, the completedTodosCount
property could be derived automatically from the todo list. By using the @observable
and @computed
decorators we can introduce observable properties on an object:
class ObservableTodoStore {Copy the code
@observable todos = [];Copy the code
@observable pendingRequests = 0;Copy the code
Copy the code
constructor() {Copy the code
mobx.autorun(() => console.log(this.report));Copy the code
}Copy the code
Copy the code
@computed get completedTodosCount() {Copy the code
return this.todos.filter(Copy the code
todo => todo.completed === trueCopy the code
).length;Copy the code
}Copy the code
Copy the code
@computed get report() {Copy the code
if (this.todos.length === 0)Copy the code
return "<none>";Copy the code
return `Next todo: "${this.todos[0].task}". ` +Copy the code
`Progress: ${this.completedTodosCount}/${this.todos.length}`;Copy the code
}Copy the code
Copy the code
addTodo(task) {Copy the code
this.todos.push({Copy the code
task: task,Copy the code
completed: false,Copy the code
assignee: nullCopy the code
});Copy the code
}Copy the code
}Copy the code
Copy the code
Copy the code
const observableTodoStore = new ObservableTodoStore();Copy the code
Copy the code
That’s it! We marked some properties as being @observable
to signal MobX that these values can change over time. The computations are decorated with @computed
to identify that these can be derived from the state.
The pendingRequests
and assignee
attributes are not used so far, but will be used later in this tutorial. For brevity all examples on this page are using ES6, JSX and decorators. But don’t worry, all decorators in MobX have an ES5 counterpart.
In the constructor we created a small function that prints the report
and wrapped it in autorun
. Autorun creates a reaction that runs once, and after that automatically re-runs whenever any observable data that was used inside the function changes. Because report
uses the observable todos
property, it will print the report whenever appropriate. This is demonstrated in the next listing. Just press the run button:
observableTodoStore.addTodo("read MobX tutorial");Copy the code
observableTodoStore.addTodo("try MobX");Copy the code
observableTodoStore.todos[0].completed = true;Copy the code
observableTodoStore.todos[1].task = "try MobX in own project";Copy the code
observableTodoStore.todos[0].task = "grok MobX tutorial";Copy the code
Copy the code
Pure fun, right? The report did print automatically, synchronously and without leaking intermediate values. If you investigate the log carefully, you will see that the fourth line didn’t result in a new log-line. Because the report did not actually change as a result of the rename, although the backing data did. On the other hand, changing the name of the first todo did update the report, since that name is actively used in the report. This demonstrates nicely that not just the todos array is being observed by the autorun, but also the individual properties inside the todo items.
Making React reactive
Ok, so far we made a silly report reactive. Time to build a reactive user interface around this very same store. React components are (despite their name) not reactive out of the box. The @observer
decorator from the mobx-react
package fixes that by wrapping the React component render
method in autorun
, automatically keeping your components in sync with the state. That is conceptually not different from what we did with the report
before.
The next listing defines a few React components. The only MobX thing in there is the @observer decorator. That is enough to make sure that each component individually re-renders when relevant data changes. You don’t need to call setState anymore, nor do you have to figure out how to subscribe to the proper parts of your application state using selectors or higher order components that need configuration. Basically, all components have become smart. Yet they are defined in a dumb, declarative manner.
Press the Run code button to see the code below in action. The listing is editable so feel free to play with it. Try for example to remove all the @observer calls, or just the one decorating the TodoView. The numbers in the preview on the right highlight each time a component is rendered.
@observerCopy the code
class TodoList extends React.Component {Copy the code
render() {Copy the code
const store = this.props.store;Copy the code
return (Copy the code
<div>Copy the code
{ store.report }Copy the code
<ul>Copy the code
{ store.todos.map(Copy the code
(todo, idx) => <TodoView todo={ todo } key={ idx } />Copy the code
)}Copy the code
</ul>Copy the code
{ store.pendingRequests > 0 ? <marquee>Loading... </marquee> : null }Copy the code
<button onClick={ this.onNewTodo }>New Todo</button>Copy the code
<small> (double-click a todo to edit)</small>Copy the code
<RenderCounter />Copy the code
</div>Copy the code
);Copy the code
}Copy the code
Copy the code
onNewTodo = () => {Copy the code
this.props.store.addTodo(prompt('Enter a new todo:','coffee plz'));Copy the code
}Copy the code
}Copy the code
Copy the code
@observerCopy the code
class TodoView extends React.Component {Copy the code
render() {Copy the code
const todo = this.props.todo;Copy the code
return (Copy the code
<li onDoubleClick={ this.onRename }>Copy the code
<inputCopy the code
type='checkbox'Copy the code
checked={ todo.completed }Copy the code
onChange={ this.onToggleCompleted }Copy the code
/>Copy the code
{ todo.task }Copy the code
{ todo.assigneeCopy the code
? <small>{ todo.assignee.name }</small>Copy the code
: nullCopy the code
}Copy the code
<RenderCounter />Copy the code
</li>Copy the code
);Copy the code
}Copy the code
Copy the code
onToggleCompleted = () => {Copy the code
const todo = this.props.todo;Copy the code
todo.completed = ! todo.completed;Copy the code
}Copy the code
Copy the code
onRename = () => {Copy the code
const todo = this.props.todo;Copy the code
todo.task = prompt('Task name', todo.task) || todo.task;Copy the code
}Copy the code
}Copy the code
Copy the code
ReactDOM.render(Copy the code
<TodoList store={ observableTodoStore } />,Copy the code
document.getElementById('reactjs-app')Copy the code
);Copy the code
Copy the code
The next listing shows nicely that we just have to alter our data without doing anything else. MobX will automatically derive and update the relevant parts of the user interface again from the state in the store.
const store = observableTodoStore;Copy the code
store.todos[0].completed = ! store.todos[0].completed;Copy the code
store.todos[1].task = "Random todo " + Math.random();Copy the code
store.todos.push({ task: "Find a fine cheese", completed: true });Copy the code
// etc etc.. add your own statements here...Copy the code
Copy the code
Working with references
So far we have created observable objects (both prototyped and plain objects), arrays and primitives. You might be wondering, how are references handled in MobX? Is my state allowed to form a graph? In the previous listings you might have noticed that there is an assignee property on the todos. Let’s give them some Values by introducing another “store” (OK, it’s just a glorified array) containing people, and assigning tasks to them.
var peopleStore = mobx.observable([Copy the code
{ name: "Michel" },Copy the code
{ name: "Me" }Copy the code
]);Copy the code
observableTodoStore.todos[0].assignee = peopleStore[0];Copy the code
observableTodoStore.todos[1].assignee = peopleStore[1];Copy the code
peopleStore[0].name = "Michel Weststrate";Copy the code
Copy the code
We now have two independent stores. One with people and one with todos. To assign an assignee to a person from the people store, we just assigned a reference. These changes will be picked up automatically by the TodoView. With MobX there is no need to normalize data first and to write selectors to make sure our components will be updated. In fact, it doesn’t even matter where the data is stored. As long as objects are made observable, MobX will be able to track them. Real JavaScript references will just work. MobX will track them automatically if they are relevant for a derivation. To test that, just try changing your name in the next input box (make sure you have pressed the above Run code button first!) .
Your name:
By the way, the HTML of the above input box is simply:
<input onkeyup="peopleStore[1].name = event.target.value" />Copy the code
Asynchronous actions
Since everything in our small Todo application is derived from the state, it really doesn’t matter when state is changed. That makes creating asynchronous actions really straightforward. Just press the the following button (multiple times) to emulate asynchronously loading new todo items:
The code behind that is really straightforward. We start with updating the store property pendingRequests to have the UI reflect the current loading status. Once loading is finished, we update the todos of the store and decrease the pendingRequests counter again. Just compare this snippet with the earlier TodoList definition to see how the pendingRequests property is used.
observableTodoStore.pendingRequests++;
setTimeout(function() {
observableTodoStore.addTodo('Random Todo ' + Math.random());
observableTodoStore.pendingRequests--;
}, 2000);Copy the code
DevTools
The mobx-react-devtools package provides the devtools that you find on the top right of this screen and which can be used in any MobX + ReactJS app. Clicking the first button will highlight each @observer component that is being re-rendered. If you click the second button and after that one of the components in the preview, the dependency tree of that component is shown so that you can precisely inspect which pieces of data it is observing at any given moment.
Conclusion
That’s all! No boilerplate. Just some simple, declarative components that form our complete UI. And which are derived completely, reactively from our state. You are now ready to start using the mobx and mobx-react packages in your own applications. A short summary of the things you learned so far:
- Use the
@observable
decorator orobservable(object or array)
functions to make objects trackable for MobX. - The
@computed
decorator can be used to create functions that can automatically derive their value from the state. - Use
autorun
to automatically run functions that depend on some observable state. This is useful for logging, making network requests, etc. - Use the
@observer
decorator from themobx-react
package to make your React components truly reactive. They will update automatically and efficiently. Even when used in large complex applications with large amounts of data.
Feel free to play around a bit longer with the editable code blocks above to get a basic feeling how MobX reacts to all your changes. You could for example add a log statement to the report
function to see when it is called. Or don’t show the report
at all and see how that influences the rendering of the TodoList
. Or show it only under specific circumstances…
MobX is not a state container
People often use MobX as alternative for Redux. But please note that MobX is just a library to solve a technical problem and not an architecture or even state container in itself. In that sense the above examples are contrived and it is recommended to use proper engineering practices like encapsulating logic in methods, organize them in stores or controllers etc. Or, as somebody on HackerNews put it:
“MobX, it’s been mentioned elsewhere but I can’t help but sing its praises. Writing in MobX means that using controllers/ dispatchers/ actions/ supervisors or another form of managing dataflow returns to being an architectural concern you can pattern to your application’s needs, Rather than being something that’s required by default for anything more than a Todo app.”
Learn more
Intrigued? Here are some useful resources:
- MobX on GitHub
- Api documentation
- (Blog) Making React reactive: the pursuit of high performing, easily maintainable React apps
- (Blog) Becoming fully reactive: an in-depth explanation of Mobservable
- JSFiddle with a simple todo app
- MobX, React, TypeScript boilerplate
- MobX, React, Babel boilerplate
- MobX demo from Reactive2015 conference
- MobX + React TodoMVC
Tweet