MobX: a Redux alternative you should consider

Let’s talk about state management in React. Or to be precise, why you might use MobX instead of Redux.

Recently we worked on the React Native app for Prepd and needed a state management solution. Easy task, isn’t it? That’s what we thought as well, so our default approach was to use Redux. Was there a particular reason to choose Redux? Not really, we just knew it was “the best solution” out there.

Redux is not for the faint-hearted

To be honest, we didn’t really ask ourselves how complex our project would be, how much state we’d need to manage, or what amount of data we’d have to deal with.

When I was mentally going through the process of building the whole thing with Redux, I began to feel a bit overwhelmed and had a lot of questions:

  • How much data are we loading via the API? Does each of the calls have its own Redux action?
  • Do we have to wait for all API calls to complete before showing any data?
  • How do we handle navigation with Redux? Is this part of the state as well, needing to be reflected in the reducers?
  • Do we implement jumping back and forth between screens ourselves, or does that happen automatically with Redux?
  • How many reducers do we need for the content? Should each nested data object have its own reducer?

As you can see, I already had a lot of doubts about using Redux because I wasn’t experienced enough to come up with the right solutions. From the few things I had learned about Redux, I knew that even setting up the basic infrastructure can get incredibly complex.

A diagram visualising Redux core concepts: Action creators, store and reducers
Managing app state with Redux: not an easy task if you’re unfamiliar with its core concepts

Later in the development process, it emerged that fetching live API data was irrelevant: that became part of our app build process for the initial app release. The navigation challenges were resolved when we used a solid plugin. And jumping back and forth between screens was taken care of by ExNavigation’s internal mechanism, so there was no need to worry about that either.

Thinking about these questions and how to structure our app was really just a chimera. I think this is true for many people who approach state management for the first time: we immediately aim for the most sophisticated solution out there, even if it’s not the right tool for the job.

Even Dan Abramov (the creator of Redux) and Michael Jackson (the creator of React Router) advocate for using simpler solutions for state management until your app gets seriously complex.

How we ended up using MobX

Luckily, we discussed this problem in the initial phase of the project, and one team member advocated for using MobX instead of Redux. I didn’t know a lot about it at that time, but it was said to be more automatic and less complex.

That began to sound like a much more appealing decision: by this point of the project the state management challenges had become much more manageable. We were very reluctant to risk getting sucked into a lot of structural optimisation before we could even build our first screen.

Four screenshots of the Prepd app showing the lunchbox list, a lunchbox detail view, a lunchbox recipe and the shopping list
Challenge: what’s the best way to model your application state based on these screens?

It also turned out that features like a central store and explicit state weren’t so critical after all, so we could build on a more automated solution instead. Given our time constraints, MobX was the ideal starting point as it didn’t dictate how to model our data, and it didn’t require us to structure our application around it either.

How we solved state management in our app

Since MobX doesn’t involve stores, actions and dispatchers, we just started out implementing our user interface and incorporated new MobX observables into our app logic as we needed them:

  • We started out by using a simple array of objects (in our case: ‘lunchboxes’) that mocked the content later provided by the API
  • Later, this array was replaced by a reference to a lunchbox object store which was responsible for initialising this store array and filling it up with lunchbox objects. This store is an @observable, and components could consume it as a data source.

    Code extract from the Prepd app showing the LunchboxStore class and its constructor method
    The LunchboxStore object that contains the “lunchboxes” @observable formed our data backbone
  • We had three different types of lists (unfiltered, filtered and favourites), each of them derived from the original lunchbox list, so using a @computed value made sense here, since it would allow us to return the original lunchbox array, pulled through a custom filter

    A typical MobX code snippet showing a getter function wrapped with a @computed decorator
    Other variants of the lunchboxes array could be easily generated using @computed and JS filters
  • Later, we split up the store objects into more specific stores, each of them responsible for a separate chunk of content. That’s how we ended up building an OOP-style chain of objects: lunchboxes contains recipes contains ingredients

The app components were structured in a similar way, where the top component (the LunchboxList) would contain the app logic and the lunchbox array. Every time this changed, the changes would trickle down the component hierarchy to the presentational components that were just displaying the content.

Diagram visualising how we managed data flow in the Prepd app using MobX
A uni-directional data-flow with MobX: changes to an @observable affect all its @computed descendant

If something had to change inside a component when the state updates, wrapping an @observer around its class guaranteed that it would respond to all state changes that happen to the @observable variables it is consuming.

Code snippet that shows a dataSource getter function using references to other external objects
Wrapping a component in @observer, it’ll respond to updates to all the object stores it is referencing

For example, if the user changes her lunchbox preferences, the ListView displaying the list of lunchboxes would automatically update. That’s because Mobx listens to changes on the preferences value (which is an @observable), and the filtered lunchboxes array (which is a @computed value) depends on that observable. So it automatically updates the application state, and all the @observer components accessing those values. Pretty smart, right?

Some things I loved with MobX, but which others might not love so much

For us, MobX worked like a charm. I immediately fell in love with the concept of observable values (when one thing updates, everything that depends on it gets updated as well), which remove the need to dispatch an action, build complex reducers, and have a growing repository of actions that your app can dispatch.

A highly abstracted view of how data flows in a MobX application
A slightly simplified view of how MobX works

As pointed out by others in the React community, MobX is based on an object-oriented view of the world: you have components inheriting information from other components, and a heavy use of the observer pattern.

With the rise of reactive functional programming, this paradigm naturally attracts a lot of resentment from more progressive app developers that prefer using cutting edge concepts and technologies such as Redux. And I don’t have anything against that, but with this post I wanted to make a little case for MobX, because it really helped us speed up our development process on this project and I think it’s a good fit if you don’t have to manage an extremely complex application state.

By the way: for a quick introduction to MobX, head over to egghead.io for a course by its very own creator, Michael Weststrate.

And if you’re looking for help building your own mobile apps, take a look at our approach to building native mobile apps with React Native.