MobX-state-tree: A step by step guide for React Apps

Faris Tangastani
6 min readOct 22, 2018

--

Developers who are familiar with Redux know how important and useful it is to have a storage and good control of your app’s state. MobX is seen as an alternative to Redux, with slight different features that make it very interesting for building smaller applications quickly and efficiently.

What is MobX-state-tree?

MobX-state-tree runs on top of MobX (see MobX as almost the engine) and tries to provide the perfect balance between immutability and mutability. It is also very opinionated which helps to solve problems that you may face when starting a project since it provides a specific structure to build your app (MobX itself is non-opinionated). Here a core concept of MobX-state-tree from the documentation:

Central in MST (mobx-state-tree) is the concept of a living tree. The tree consists of mutable, but strictly protected objects enriched with runtime type information. In other words, each tree has a shape (type information) and state (data). From this living tree, immutable, structurally shared, snapshots are automatically generated”.

The tree itself is mutable, however we can create different immutable snapshots so as to travel back to easier if needed be. Lets just jump into the building our first app with MobX and MobX-state-tree in React!

Setup

In order to quickly setup an app with no build-configuration I will use Create-React-App. Its an awesome tool to get started straight away with React and has many cool features out of the box.

npx create-react-app simple-app
cd simple-app

We are going to need to install a few things before we can begin working with MobX and MobX-state-tree.

npm install mobx mobx-react mobx-state-tree

We also need some folders for our components and our models in our src folder.

mkdir models components containers

We can then move our App.js into our containers folder for extra organization of our App.

mv App.js containers

Ok! We have everything setup to start coding, so your structure should look something like this:

Now lets start by making a model to setup our state for the app. Since we are creating a simple Todo app, lets create two models. TaskStore.js to act as our store for creating Todos and Todo.js to define what a Todo will look like.

// models/TaskStore.jsimport { types } from 'mobx-state-tree';
import Todo from './Todo';
const TaskStore = types.model('Todo', {
Todo: types.array(Todo)
}).actions(self =>({
add(task){
self.Todo.push(task);
}
}));
export default TaskStore;

With both the Todo model and the TaskStore model we define actions specific to that model to interact with that data. Any fact that derived from the state is called a ‘view’ / ‘derivation’. This can be a computed or non computed value but it is derived directly from the state. Inside the views, we are going to make a function called status which watches whether the Todo field ‘is_done’ is true or false and returns a string according to that.

// models/Todo.jsimport { types } from 'mobx-state-tree';const Todo = types.model('Todo', {
name: types.string,
details: types.string,
is_done: false,
}).actions(self =>({
markDone(){
self.is_done = true;
}
})).views(self =>({
status(){
return self.is_done ? "Done" : "Not Done"
}
}));
export default Todo;

Lets also create a Form and TodoList component. The form to add todos and the TodoList to display the todos.

// components/Form.jsimport React from 'react';export default class Form extends React.Component  {
render(){
return(
<form
onSubmit={e =>{
e.preventDefault();
this.props.store.add({
name: this.nameInput.value,
details: this.detailsInput.value,
});
e.target.reset();
this.nameInput.focus();
}}>
<label htmlFor="name">
Name
<input
required
className
="input"
type="text"
ref={input => (this.nameInput = input)}
id="name"
/>
</label>
<label htmlFor="details">
Details
<input
required
className
="input"
type="text"
ref={input => (this.detailsInput = input)}
id="details"
/>
</label>
<button
className="btn btn-info mb-2"
type="submit">
Add
</button>
</form>
);
}
}

As you see here we use the stores actions to add a todo with the form component.

// components/TodoList.jsimport React from 'react';
import { observer } from 'mobx-react';
import Form from './Form';
const TodoList = ({todo}) => {return(
<div className="card" >
<div className="card-body">
<h4 className="card-title">{todo.name}</h4>
<div>
{todo.details}
</div>
</div>
<div className="status" >
{todo.status()}
</div>
{!todo.is_done &&
<button
className="btn btn-info mb-2"
onClick={todo.markDone}
>
Done
</button>}
</div>
)}
export default observer(TodoList);

We will use the observer method from mobx-react to change this component into a reactive-component. If any data that is used in the component gets updated, it will automatically re-render the TodoList. The TodoList component will also use the status function from the stores views to check the status of our todo. If the todo is marked as done it will automatically update the DOM!

Now lets setup our App to receive the store so we can use the functionality we just built. We do this by injecting the store into the components props with the inject method. Now we are able use it as we do with <Form store={store}/> as well as with the mapping of our<TodoList todo={todo} key={i} /> component.

//containers/App.jsimport React, { Component } from 'react';
import { observer, inject } from 'mobx-react'; //These functions make our components observable and be able to use the store
import TodoList from '../components/TodoList';
import Form from '../components/Form';
import logo from '../logo.svg';
import '../App.css';
class App extends Component {render() {
const { store } = this.props;

return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
</header>
<div>
<h3 className="subtitle">Make a new To do</h3>
<Form store={store}/>
</div>
<div className="card-container">
{store.Todo.map((todo, i) => (
<TodoList todo={todo} key={i}/>))}
</div>
</div>
);
}
}
export default inject('store')(observer (App));

Finally we create the store in index.js and use the Provider to be able to access this in our App element. Lets also add a debugging tool to make it easier to debug state changes and have a better overview of the project with mobx-devtools-mst. This is a chrome extension so make sure to download that if using chrome. If your are not using chrome, we can also use the onPatch from MobX-state-tree, I have added both for clarity.

//index.js

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'mobx-react';
import './index.css';
import App from './containers/App';
import * as serviceWorker from './serviceWorker';
import TaskStore from './models/TaskStore';
//debugging toolsimport { onPatch } from 'mobx-state-tree';
import makeInspectable from 'mobx-devtools-mst';
onPatch(store, patch => {
console.log(patch)
})
const store = TaskStore.create({})
makeInspectable(store);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>, document.getElementById('root'));
// If you want your app to work offline and load faster, you can change// unregister() to register() below. Note this comes with some pitfalls.// Learn more about service workers: http://bit.ly/CRA-PWAserviceWorker.unregister();

This was a quick demonstration of how to build a simple application with MobX using MobX-state-tree. As we have seen, if you walk through it step by step, MobX is quick and efficient in use. MobX-state-tree allows us to compartmentalize our storages and makes it easier to understand the flow of data through any app. Feel free to try it out yourself!

Thank you for reading! Check my Github or my other stories:

--

--