Managing State with MobX
From this post, you will learn what MobX is, what it is useful for, the benefits of MobX and some major MobX concepts. We will then go through a quick tutorial describing briefly how to use MobX in a React application.
What is MobX?
MobX is simply a state management library.
A primary concern related to building single page applications is having to deal with data changes over time (especially in a client-server application) and re-rendering based on these changes.
Here comes state management…
State management basically means managing the state of the application or data presented to the user at any given time.
To properly manage state, it is very important to be able to track:
- Whether the state changes.
- What the state was before the change.
- Whether the state will change again
In single page applications, there will be interactions with the data at different times with the components. It is important to be able to handle these changes when they occur.
Why MobX?
With MobX, the application state is synchronized with your component by using a reactive virtual dependency state graph, internally. It is only updated when needed and is never stale. The following are reasons why you may want to have MobX in your application:
- It is very simple to use and therefore speeds up development considerably.
- With MobX, you do not need to normalize your data.
- Modifying state is very straightforward. You simply write your intentions and MobX handles the rest.
- MobX automatically tracks changes between states and derivations. As a developer, you might forget that changing some data would influence a seemingly unrelated component, but MobX does not.
Major MobX concepts
Observable State
Observable capabilities are added to data structures, arrays, or class instances with the use of the observable
decorator.
import { observable } from “mobx”; class WorkoutStore {
@observable completed = false;
}
Using
observable
can be likened to turning the object property to a cell in the spreadsheet, except that values are objects, arrays, and references and not primitive values.
Computed values
They are values derived from state. Using computed
decorator defines values that will be derived automatically when relevant data is modified.
class WorkoutStore {
@observable workouts = [];
@computed get UncompletedWorkoutsCount() {
return this.workouts.filter(workout => !workout.completed).length;
} }
Here UncompletedWorkouts
is updated automatically when a workout
is added or when any completed
property is modified.
Reactions
Reactions produce a side effect instead of producing a new output (like the case of computed values) when a new change occurs. You can make your stateless components reactive by adding the observer function/decorator from the mobx-react
package.
import React, {Component} from 'react';
import ReactDOM from 'react-dom';
import {observer} from 'mobx-react'; @observer class AllWorkouts extends Component {
render() {
return
<div>
<ul>
{this.props.workoutList.workouts.map(
workout => <Workout workout={workout} key={workout.id}
/>
)}
</ul>
Workouts left:
{this.props.workoutList.unfinishedWorkoutCount} </div>
} }
const Workout = observer(({workout}) =>
<li>
<input type="checkbox"
checked={workout.finished}
onClick={() => workout.finished = !workout.finished}
/>{workout.title}
</li> )
const store = new WorkoutList();
ReactDOM.render(<AllWorkouts workoutList={store} />, document.getElementById('mount'));
- React components are turned into derivations of the data they render with
observer
. MobX ensures that the components are re-rendered when needed.
Actions
Any piece of code that tries to alter the state is an action. It takes in a function and returns a function. Functions that perform look-ups, filters, etc. should not be marked as actions. Actions should only be used on functions that modify state. Actions are usually marked with the @action decorator.
Quick tutorial
Let’s set up a quick React application called FriendList with MobX.
- We start with setting up the React project using the
create-react-app
package
yarn install create-react-app
create-react-app friend list
- Run
yarn start
and view the app as shown:
yarn start
- Now to use decorators, we need to modify the configuration of the React app, so first of all we run:
yarn run eject
This basically moves the build dependencies into our project and allows for customization.
- Next, we run:
yarn add babel-plugin-transform-decorators-legacy
Go to package.json
, look for the babel
section, and add ‘transform-decorators-legacy’ plugin.
By doing these two steps, we are able to use decorators in the application.
- Next, we install
mobx
andmobx-react
yarn add mobx mobx-react
Now that we have MobX set up, let’s have some content:
- Go to
src
folder and create astores
folder.
In MobX, we can have different stores that handle state for one piece of the application. For this demo, we would create one store calledFriendStore
.
- Up next, we want to fill up our store:
Import observable
, action
, and computed
from mobx
.
Create a class called FriendStore that is made up of a property called friends
, which is an observable (things that we want to keep track of).
We create an action called addFriend
.
Finally, create a computed property called friendCount that returns the number of friends.
import { observable, action, computed } from "mobx";class FriendStore {
@observable friends = []; @action addFriend(friend) {
this.friends.push(friend);
} @computed get friendCount() {
return this.friends.length;
}}const store = new FriendStore();
export default store;
- Next, we go into the
index.js
file:
Import Provider
from mobx
.
Wrap app component with Provider, then render the Root
constant created, which contains the Provider and App.
Import FriendStore and pass as props to the Provider.
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';import { Provider } from 'mobx-react';
import FriendStore from './stores/FriendStore';const Root = (
<Provider FriendStore={FriendStore}>
<App/>
</Provider>
)ReactDOM.render(Root, document.getElementById('root'));
registerServiceWorker();
Finally, time to show something to the user!
We want to have an input field to add friend and a list to show the friends added:
- Go to
App.js
. - We import
inject
andobserver
frommobx-react
. - With the decorators (inject and observer), we inject the
FriendStore
in the application and make the component an observer (which basically watches the store for when data changes). - We create a form to enter and submit a friend.
import React, { Component } from 'react';
import { inject, observer } from 'mobx-react';@inject('FriendStore')
@observerclass App extends Component { handleSubmit = (e) => {
e.preventDefault();
const friend = this.friend.value;
this.props.FriendStore.addFriend(friend);
this.friend.value = '';
} render() {
const {FriendStore} = this.props;
return (
<div className="App">
<h2>You have {FriendStore.friendCount} friends.</h2>
<form onSubmit={e => this.handleSubmit(e)}>
<input type="text" placeholder="Enter friend" ref={input => this.friend = input}/>
<button>Add Friend</button>
</form>
<ul>
{FriendStore.friends.map(friend => (
<li key={friend}>{friend}</li>
))}
</ul>
</div>
);
}
}export default App;
You may encounter an experimental Decorators
warning if you are using VSCode. To cater for this, simply create a tsconfig.json
file in the root directory of your project and include the following options and then restart VSCode.
{
"compilerOptions": {
"experimentalDecorators": true,
"allowJs": true
}
}
Finally, run yarn start
and Voila..
Here is the link to the github repo.
I hope this has been helpful in getting you up to speed with MobX. Feel free to ask questions in the comment section.