A MobX introduction and case study

  • Actions modify the state, which triggers reactions.
  • Overall, we are very happy with the transition to MobX and would recommend investigating it if you are looking for a state management solution, especially if you are using TypeScript or Flow.
  • To use the previous example, if I wanted to display a banner after adding a client in that component I could set this.client = true and render something different when this.client is true.
  • Both name and numberPeople are observable properties of the thisMeetup object but only numberPeople is used in the autorun function.
  • Another neat thing about using MobX for state is that changes will be logged if you are using dev tools.

We Are Wizards Blog

@ReactNext: Introduction to #MobX with a case study of real world #reactjs app

Writing proposals? Try Proppy, our new product!

Reading time: ~15 minutes.

MobX (previously mobservable) is a state management library for JavaScript frontend application. This article introduces it with examples as well as showing how it works in a real app with TypeScript. It is based on a talk I gave at the Osaka Web designers and developers meetup recently.

Let’s start by explaining what is MobX and how it works. It’s presented as using Transparent Functional Reactive Programming.

Now, FRP is a very controversial term as everyone seems to have their own definition so let’s forget about that and look at a illustration from the MobX docs (click on it to open in a new tab and get full size or open this link).

In short, actions modify the state, which triggers reactions. Part of the state can be derived automatically, such as the number of tasks left to do in a TODO list to take the example of the picture above. What sets MobX apart from other Observable implementations is the transparent part. Reactions observe which observables you are using and subscribe to them automatically, without you having to explicitely subscribe to those.

Before we continue, a few more things:

This part is written from my experience using both Redux/ImmutableJS and MobX with React in Proppy, our app to write proposals for freelancers and small agencies/companies. Its frontend is written in TypeScript, which was one of a big reason to moving to MobX as we will see in a bit.

Our issues with Redux/ImmutableJS were twofold.

The first issue was that it is very verbose. You need to write an action, the reducer function, a selector and then connect the actions and the data on the React components.

was common and there was no typecheck whatsoever.

With MobX we only have to mention the types and, as we will see when showing examples of Proppy, get full typechecking as we are only dealing with plain JavaScript objects.

Note that using MobX means you have to give up immutability, which could be a showstopper for you.

This section is a brief introduction to MobX. If you have already used it, you can safely skip to the next section to see some patterns/snippets from a live application.

Note that I will use ES6 syntax and decorators throughout the examples but it works in plain ES5 as well.

Let’s start with observing arrays and maps, as it constitutes a big chunk of what we would want to observe.

function provided by the mobx package. This function takes a function as argument and call it once to detect which observables are being used in it and subscribe to them.

doesn’t actually run everytime any observable is modified like the example above might indicate but only when one of those used in the function are modified.

The other thing that we typically want to observe are class properties.

function. Therefore, the following snippet will not print anything:

Remember the T part of TFRP? MobX automatically figured out which observable to subscribe to and has done so transparently.

solves that issue by not triggering reactions until the end of the function.

equal to 4, not with the intermediate 2 and 3.

is lazy (won’t be evaluated unless needed in a reaction) and it is memoized (if the observables used inside didn’t change, it doesn’t need to re-run and can return the current value).

can only be used on getter in a class.

Actions are functions that modify the state. While, as shown in the previous examples, we can simply modify the object directly, it is not the recommended way to do so.

function or decorator. This will accomplish a few things:

in your app.

We don’t use the strict mode ourselves

Now that you know the basics of MobX, it’s time to see how it looks in production. It’s currently the backbone of our frontend app in Proppy so we will use examples from it.

If you are doing SSR, be aware that the way we handle stores shown below will not work for you.

Converting Proppy to use MobX took about 4 days, numerous long-standing issues were fixed along the way and the end result was a deletion of around 1000 to 2000 lines (estimate as we also moved to use npm @types for typings in that branch).

Our stores are simple singleton classes that have observable properties. Here’s a shorter version of one of our stores, dealing with clients for proposals.

import { observable , action , map , transaction , ObservableMap } from “mobx” ; // a class definition with types import { Client } from “./models/Client” ; import fetchling from “../utils/fetchling” ; import uiStore from “./UIStore” ; export class ClientStore { @ observable clients : ObservableMap < Client > = map < Client > (); getClient ( id : number ) { return this . clients . get ( id . toString ()); } @ action fetchAll () : Promise < any > { return fetchling ( “/clients” ). get (). then (( response : any ) => { transaction (() => { this . setClients ( response . clients ); }); }); } @ action deleteClient ( clientId : number ) { const client = this . clients . get ( clientId . toString ()); return fetchling ( ` / clients / $ { clientId } ` ). delete () . then (() => { this . clients . delete ( clientId . toString ()); uiStore . notify ( ` Client deleted ` , false ); }) . catch (() => uiStore . notify ( ` Client $ { client . name } could not be deleted . Please try again later ` , false )); } @ action updateClient ( clientId : number , name : string ) { return fetchling ( ` / clients / $ { clientId } ` ). put ({ name }) . then (( response : any ) => { this . clients . set ( clientId . toString (), new Client ( response . client )); uiStore . notify ( ` Client updated ` , false ); }) . catch ( response => { uiStore . notify ( ` Client could not be updated . Please try again later ` , true ); if ( response . errors ) { return response . errors . errors ; } }); } } const clientStore = new ClientStore (); export default clientStore ;

Quite simple stuff. You can see we are calling another store from our store. That would be a big no-no in Redux and we were using a middleware to solve that previously. It feels much nicer now: we have a full view of what’s happening and it’s very easy to see if something is missing (which was actually the case for some ajax requests).

would be a compiler error. It really shines for complex objects like cost tables that were previously a simple map with no schema and are now fully typed objects with functions of their own. Thanks to that, we moved things such as calculating totals to the cost table class rather than a React component like before.

Since we don’t do server side rendering, we can directly import a store and call its function directly. Here’s a slightly modified code sample for the Settings>Clients page:

import * as React from “react” ; import { observer } from “mobx-react” ; import { observable } from “mobx” ; import companyStore from “../../stores/CompanyStore” ; import clientStore from “../../stores/ClientStore” ; // … some component import @ observer export class Clients extends React . Component < {}, {} > { // not used, only here for the sake of example @ observable clientAdded : boolean = false ; // Our async loaded component will call that function and wait until // the promises complete to render that component static fetchData () { return Promise . all ([ companyStore . fetchUs (), clientStore . fetchAll (), ]); } renderClients () { return clientStore . clients . values (). map ( client => { return ( < div className = "client" key = { client . id } > < span onClick = {() => clientStore . deleteClient ( client . id )} className = “icon-delete” /> < InlineInput value = { client . name } onEnter = {( value ) => clientStore . updateClient ( client . id , value )} /> < /div> ); }); } renderForm () { return ( < InAppForm inline = { true } resetOnSuccess submitText = "Add client" onSubmit = {( data ) => clientStore . createClient ( data )} /> ); } render () { return ( < SettingsContainer > < div className = "clients" > < h2 > Manage { your } clients < /h2> { this . renderForm ()} < div className = "clients-list" > { this . renderClients ()} < /div> < /div> < /SettingsContainer> ); } }

for example would be a compilation error and I only had to write the type once: in the store as shown before.

to remove the need for React state.

to satisfy the TypeScript compiler. Another neat thing about using MobX for state is that changes will be logged if you are using dev tools.

is available and is pretty great. See the gif below from its repo to have an overview of the 3 features.

I have mostly used the logging to inspect action and state changes while debugging and to spot erroneous updates and the re-rendering highlighter to figure out if some of our components were re-rendering too often.

Overall, we are very happy with the transition to MobX and would recommend investigating it if you are looking for a state management solution, especially if you are using TypeScript or Flow. I’m pretty impressed by the constant work the TypeScript team has accomplished and highly recommend it as well if you are planning to stay on the JavaScript side and not go for Elm/PureScript/etc.

A MobX introduction and case study