This article is a spiritual successor to Adding MobX to a vanilla React project, inspired by many of @mwestrase's articles, and comes from a few weeks of refactoring a big Backbone app into a React+MobX app. It might not be the way to structure a MobX app, but it's a way, and it's been working well for me so far.

Design goals for this architecture are:

minimize boilerplate

your app is a state machine

flexibility to move elements

We're going to minimize boilerplate by relying on React contexts and MobX's wonderful inject function. The combination makes your data stores available everywhere in the app without having to do any wiring and passing by props. Component needs to access the state machine? Inject the store.

Your designer can reshuffle the page as much as he or she wants, and all you have to do is change where components go. No rewiring of the business logic unless the business logic itself changes. For me, this was the missing piece of the puzzle with React. Using this approach, your components become truly independent, and you can do whatever you want.

It's so much fun that I even came to work early once! If you know me, you know how much I like long mornings for myself.

Contexts and inject give us flexibility and remove boilerplate. What about the state machines?

We're going to treat our MobX stores as the only source of truth in the app and we're going to put actions into them. Actions change state and can be called from anywhere. Putting them in the store reduces boilerplate, makes sure all components have access to them, and lets you see the whole state machine in a single file.

This also makes your app easier to test. If your state machine works, everything works. This is because:

MobX in strict mode guarantees state only changes in actions

React guarantees the DOM is a pure expression of state

Yes, MobX works with mutable state and change observers and all that fun stuff. This sounds icky, if you've been listening to the past few years of functional programming propaganda about immutable state.

But you know what's cool? You get all the benefits of that anyway. MobX wraps your state changes in getters and setters, but it could potentially back those with an immutable data structure and actions could build a changelog for a time traveling debugger.

That might be a fun project. I could use a time traveling debugger for MobX ?

Models are another piece of the puzzle.

Models represent specific objects within your overall data structure. MobX stores are for state your whole app cares about; models are for state that only a specific instance of a specific React component cares about.

That sounds like it should go in component state, right? But that’s a bad idea because it makes your stuff harder to test and breaks the state machine ideal.

Models are also a great place to put actions that talk to your backend. Save, fetch, update, that sort of stuff.

You should think of this as pseudocode.

Let's build a box of kakis. I have one of those on my kitchen counter right now because they grow in my girlfriend's mom's backyard. Who knew? ¯(ツ)/¯

A box of kakis can be opened or closed. It can be sealed shut with sticky tape, and it contains kakis. Its state machine looks like this:

Once the box is open, you can add or remove as many kakis as you want. You can close the box from any N kaki state, but you have to open it to add or remove kakis. You can seal it only when it's closed and open it only when it's unsealed.

As a MobX store or model, your box looks like this:

import { observable , computed , action , extendObservable } from "mobx" ; export class Box { @observable sealed = true ; @observable closed = true ; @observable kakis = [ ] ; constructor ( store , initialState ) { this . store = store ; extendObservable ( this , initialState ) ; } @computed get canSeal ( ) { return this . closed ; } @computed get canOpen ( ) { return ! this . sealed ; } @computed get canManipulatekakis ( ) { return ! this . closed ; } @action addkaki ( ) { this . kakis . push ( new kaki ( ) ) ; } @action takekaki ( ) { this . kakis . pop ( ) ; } @action open ( ) { if ( this . canOpen ) { this . opened = true ; } } @action close ( ) { this . closed = true ; } @action seal ( ) { if ( this . canSeal ) { this . sealed = true ; } } @action unseal ( ) { this . sealed = false ; } }

@observable is a MobX decorator that makes properties observable so MobX can do its thing. extendObservable is a convenient way to set a bunch of observable values on an object. @computed marks some properties as deriving purely from state so MobX can memoize them, and @action marks methods as actions.

Look at the state machine picture. Observables and computeds put together are the circles (states), and actions are the arrows (transitions between states).

So much work for a box, right? But look what happens when we put this in a React component:

import { Provider } from "mobx-react" ; import { Box } from "./models/Box" ; class MainStore { @observable box = null ; @action getBoxFromMail ( ) { this . box = new Box ( { sealed : true , closed : true , kakis : [ new kaki ( ) , new kaki ( ) ] , } ) ; } } class App extends Component { mainStore = new MainStore ( ) ; render ( ) { const mainStore = this . mainStore ; return ( < provider mainstore = { mainStore } > < div > < button onclick = { mainStore . getBoxFromMail . bind ( mainStore ) } > Get Mail < / button > < box > < / box > < / div > < / provider > ) ; } }

We set up a new MainStore , which holds the main state for our app. Stuff that everyone needs like have you picked up the box of kakis from the mail yet, is your counter black, is your refrigerator running. That sort of thing.

Inside the render function, we use Provider to add mainStore to a React context. Any component inside Provider can get access to mainStore if it needs to.

The nice thing about bundling actions with your stores and models is that you can use them in onClick handlers. Which means most of your components can be stateless functional components.

Let me show you.

import { inject , observer } from 'mobx-react' ; const SealedOrOpened = observer ( ( { box } ) => ( if ( box . sealed ) { return 'Sealed and Closed' ; } else if ( ! box . sealed && ! box . opened ) { return 'You should open the box' ; } else { return 'Take some kakis!' ; } ) ) ; const If = ( { cond , children } ) => cond ? children : null ; const Box = inject ( 'mainStore' ) ( observer ( ( { mainStore } ) => { const box = mainStore . box ; return ( < div > < sealedoropened box = { box } > < if cond = { box . canOpen } > < button onclick = { box . open . bind ( box ) } > Open box < / button > < / if > < if cond = { box . opened } > < ul > { box . kakis . map ( kaki => < kaki > ) } < / kaki > < / ul > < button onclick = { box . takekaki . bind ( box ) } > Take a kaki < / button > < / if > < / sealedoropened > < / div > ) ; ) ) ;

That's the Box component in spirit. It renders some text telling you what to do with the box and gives you a button that opens it. Yes, I know there should be a button to take off the sticky tape, too.

Once you open the box, a list of kakis appears and a button to take them out one by one.

Notice that Box is a functional stateless component. You can do that because all the state and all the actions are in the Box MobX model. There’s no need to build local click handlers.

You might also notice that we inject('mainStore') 'd into the Box component. That lets us move the component to anywhere within the DOM tree inside Provider and it will continue to work. No changes necessary.

Your designer will love the flexibility, and your PM will fart rainbows at how fast you are.

Oh, and that observer thingy? That ensures your components re-render when state changes.

Next week, I'm going to write about persisting those changes to a backend of some sort. I’m still working on how to make that part effortless and clean :)

Maybe I should make a Building Webapps With React and MobX book ? Tell me if you're interested.

Did you enjoy this article? 👎 👍

Published on November 4th, 2016 in Front End, react, Technical

Learned something new?

Want to become a high value JavaScript expert? Here's how it works 👇 Leave your email and I'll send you an Interactive Modern JavaScript Cheatsheet 📖right away. After that you'll get thoughtfully written emails every week about React, JavaScript, and your career. Lessons learned over my 20 years in the industry working with companies ranging from tiny startups to Fortune5 behemoths. Start with an interactive cheatsheet 📖 Then get thoughtful letters 💌 on mindsets, tactics, and technical skills for your career. "Man, love your simple writing! Yours is the only email I open from marketers and only blog that I give a fuck to read & scroll till the end. And wow always take away lessons with me. Inspiring! And very relatable. 👌" ~ Ashish Kumar Your Name Your Email Your Address Subscribe & Become an expert 💌 Join over 10,000 engineers just like you already improving their JS careers with my letters, workshops, courses, and talks. ✌️

Have a burning question that you think I can answer? I don't have all of the answers, but I have some! Hit me up on twitter or book a 30min ama for in-depth help.

Ready to Stop copy pasting D3 examples and create data visualizations of your own? Learn how to build scalable dataviz components your whole team can understand with React for Data Visualization

Curious about Serverless and the modern backend? Check out Serverless Handbook, modern backend for the frontend engineer.

Ready to learn how it all fits together and build a modern webapp from scratch? Learn how to launch a webapp and make your first 💰 on the side with ServerlessReact.Dev

Want to brush up on your modern JavaScript syntax? Check out my interactive cheatsheet: es6cheatsheet.com

By the way, just in case no one has told you it yet today: I love and appreciate you for who you are ❤️