Why Infinite Red uses MobX-State-Tree instead of Redux

Is Redux the best choice for React apps today?

Jamon Holmgren
Red Shift
Published in
11 min readJun 16, 2020

For the past five years, Redux has dominated React and React Native app state management. So why would one of the leading React and React Native development shops abandon it in favor of MobX-State-Tree?

Update February, 2021: I’ve taken over as lead maintainer of MobX-State-Tree, so throw that caveat in. However, I wrote this article several months before doing that, and we made the decision to move to MST many years prior. — Jamon

Our Story Begins

In October of 2015, my mobile development team at Infinite Red decided to make the switch from native apps to React Native. At the same time, it made sense to switch from Ember to React.

Almost immediately, we were faced with the question of app-wide state management. Luckily, Redux was very popular and seemed to solve the problems we had. So we adopted Redux and dove headfirst into React and React Native development.

Fast-forward to 2017. Our teams, while proficient with Redux, had been dealing with some pain points with it. So we started casting around for a potential replacement. MobX was an intriguing option and a popular state management system, but didn’t have enough structure for us. After experimenting with MobX-State-Tree (MST) on a new project, we adopted it and have been using it ever since for both React web and React Native projects.

What is MobX-State-Tree?

https://github.com/mobxjs/mobx-state-tree

Well, you can watch my talk at React Native EU 2019, or just read on!

Technically speaking, MST is a state container system built on MobX, a functional reactive state library.

This may not mean much to you, and that’s okay. I’ll explain it like this: MobX is a state management “engine”, and MobX-State-Tree makes it feel a lot more like Redux by giving it structure.

MobX-State-Tree (MST) lets you define stores full of data, observe changes to that data, and trigger re-renders. It also lets you define actions, do side-effects, and expose “views” of the data to your React components.

Don’t worry — I’ll provide lots of code examples below so you can see what this means.

Caveat

Some people feel very strongly about MobX vs Redux:

Via the always provocative Kitze

Before I start, please note that this can be a very polarizing subject. I’m not intending to bash Redux, nor do I think you should never use Redux. I’m just explaining the thought process that went into our decision to switch back in 2017. In some cases, Redux has improved measurably since then (see the section Redux Toolkit near the end of this article). But many of these reasons are still valid.

Here are the primary factors that led us to seeking an alternative to Redux and choosing MobX-State-Tree.

1. Difficult learning curve

Redux is an implementation of the Flux pattern, introduced by Facebook in 2014. Flux is the idea of unidirectional data flow, and Redux does a good job of implementing this pattern.

Kudos to Lin Clark for this helpful illustration

One problem with the Flux pattern is that it isn’t particularly intuitive.

While Redux has some fairly well-known concepts (stores and actions), it also requires that you learn a lot of new concepts: dispatcher, reducers, action creators, thunks, sagas and epics, middleware. Actions are also message “objects” that are passed around rather than function calls, and the Redux store (of which there can only be one) is an immutable data tree.

What really tripped me up when I started with Redux was… “What?! I can’t just change the data?!” — Mark Rickert

These concepts are possible to learn — our team did — but they add a steeper learning curve to the initial parts of a project. There’s also a learning curve whenever we hand our projects off to our clients’ internal teams.

In contrast, MST’s learning curve was measurably easier for us. You have a store (or multiple stores) for your data, actions which are just functions that mutate the data, views which are just functions, and some runtime type checking. The new concepts are mostly around flows (glorified async actions) and snapshots (which are just JavaScript objects that let you work with a snapshot of your data tree).

2. Too much boilerplate

While it has gotten better, Redux is rather infamous for the amount of boilerplate code and verbosity required to implement each action.

Part of the problem is that Redux requires immutable data structures. JavaScript has gotten better at dealing with immutable data, but it’s still somewhat awkward and verbose.

Here’s a (slightly simplified) example from the official Redux examples of a shopping cart reducer containing actions to add a product to the cart and check out.

Carbon source

The same functionality in MST would looks something like this:

Carbon source

Rather than dispatching an action (like addToCart or checkout) like this:

Carbon source

… you would just call these actions like normal functions:

Carbon source

It feels much more idiomatic.

Additionally, with Redux, you usually end up with many different folders such as actions, constants, and reducers, and many files within those folders. Modifying one action would touch files in each of those, and it’s more difficult to track everything you need to change throughout the chain of events. More things in more files to keep track of on every change introduces more opportunities for errors and bugs.

When I showed the above samples to some Redux developers, they pointed out that a lot of Redux developers wouldn’t write their reducers and actions this way. (Foreshadowing section four in this article!) There is the popular Redux Ducks modular pattern and many other ways to improve the developer experience (DX) of Redux. However, since I got these from the official Redux documentation, it is probably representative of how many Redux developers would structure their reducers.

It’s also worth mentioning that some developers don’t mind having many files to touch. As Joe Lafiosca told me, “There is something peaceful and enjoyable about going through the various files to touch up a slice of my state.”

3. No clear way to handle side effects

Side effects are essentially interactions with the outside world (outside of Redux or MobX-State-Tree).

Redux doesn’t come with a built-in way to handle this. Instead, there are three main options:

Redux Thunks — arguably the simplest option, but least powerful

Redux Saga — more powerful but with a steeper learning curve

Redux Observable — similar to sagas, but based on RxJs

We used Redux Thunks first and then Redux Saga at Infinite Red until we switched to MST.

MST comes with both simple async actions (which are about as powerful as Redux Thunks) and the flow action (which is about as powerful as Redux Saga). Let’s look at an example in both.

Here’s Redux Saga (slightly modified for brevity from the Redux Saga README):

Carbon source

And here’s a similar MST flow:

Carbon source

The code is more direct and tight, and yet accomplishes everything that the Redux Saga is doing. Not only that, but it avoids the “generator state” footgun, which relies on wiring up your state changes in the right order. And lastly, testing sagas is notoriously difficult.

4. Everyone does Redux differently

Over the years, I’ve heard a lot of the same criticisms of Redux:

  • “The problem isn’t with Redux, it’s how teams use it.”
  • “People put things in app-wide stores that should be local state.”
  • “Stores are designed to support a single page and data isn’t normalized.”

While Redux has very well-documented best practices, the amount of boilerplate and indirection means that, inevitably, every team seems to implement it differently. This means that when you drop into a new project, you not only have to touch many different files to figure out what is going on, but they might not be organized in the way you’re used to.

In contrast, when we look at our projects across 3 years of MST development (2017–2020 so far), even the earliest projects use a very similar structure to what we use today, because it feels “right”. We’ve iterated on the concepts in some ways, but the basic ideas of stores, views, actions, and flows has held up very well.

MST doesn’t necessarily solve the data normalization problem, although it does provide some very useful tools for helping with this (primarily types.reference and types.safeReference). I might write another blog post about that sometime.

5. Performance, Performance, Performance

While it’s certainly possible to write performant Redux applications (we did for a couple of years), it’s not very hard to write sluggish ones.

React Redux is very well-written piece of software, but bloated middleware, unnecessary renders, un-memoized selectors, denormalized data structures, and more are all gotchas that can bite you. You can fix these with a bit of work, but you do have to be intentional about it.

MST, in contrast, makes it easy to write performant apps. MobX-React (and the lighter function-components-only version that we use, MobX-React-Lite) allows you to turn your components into observers that will automatically “watch” any data that is used within them.

Carbon source

Whenever an action changes a property, only the components that are observing those properties will re-render. So if a component only consumes userStore.currentUser.name, it won’t re-render when the avatarURL changes.

Not only that, but the components will also observe computed views!

Carbon source

In my opinion, MobX-React is the coolest part of the MobX ecosystem. You can write idiomatic React code, observe values at specific components, and have very targeted re-renders without thinking too much about it.

6. Runtime Type-Checking

While Redux and MST both have pretty good static TypeScript support, MST comes with out of the box runtime type checking, and the ability to infer TypeScript types from those runtime types so you don’t have to be writing a bunch of interfaces.

Let’s look at an example.

Notice how I never defined any TypeScript types? I got free static typechecking by doing runtime type checking. Nobody wants to do double work. This is the beauty of TypeScript type inference on MST runtime types.

In this example, MST will enforce that any newly created user must have an id, name, an optional age, a country that can only be the USA, Canada, or Mexico, and an array of roles which is a reference to a different MST model.

Not only does TypeScript enforce it while you’re coding, but if any added data is wrong at runtime, it’ll raise an exception. This is especially helpful at the API boundary level. If you define your types and then the API starts returning something that you didn’t expect, MST will alert you immediately.

If you use GraphQL on your back end, you can generate base MST models and types using mst-gql and then extend those for your app, while preserving the ability to update the base models at any time from an updated GraphQL schema. I gave a talk at Ancient City Ruby/Rails/React about mst-gql and will update this article when the talk is released.

Downsides of MobX-State-Tree

It wouldn’t be a complete article unless I mention a few downsides to our switch to MST over Redux. After all, as developers we are trained to see things in terms of tradeoffs rather than good / bad.

The primary place that MST loses to Redux is market share. I estimate (based on NPM downloads) that Redux is used by 70–80% of React developers, while MobX / MobX-State-Tree is a distant second at around 10%.

Redux also has very, very good documentation, and a ton of community resources — including dozens or hundreds of talks, screencasts, blog articles, and more. While MST’s documentation is pretty good, it doesn’t have near the community that Redux has. (Update February 2021: as lead maintainer of MST, I’ve put documentation improvement near the top of the list of things I’m working on.)

And lastly, Redux has a lot of plugins and middleware to choose from. This can be helpful for a variety of use cases. While MST supports extensions, there aren’t quite as many community libraries out there as Redux.

None of these have been dealbreakers for us, but it’s good to be aware of them.

Redux Toolkit

Since we made this switch in 2017, the Redux team has been working hard to address the community’s concerns. The biggest change has been Redux Toolkit, which adds some very nice helper functions to reduce boilerplate and increase readability. You can read more about their motivations in this blog article by Mark Erikson, but it can be summed up as making it easier to get started with Redux, add in some common defaults, and reduce boilerplate. Here’s another great article to learn more about Redux Toolkit.

Wrapping it up

Our first Ignite CLI boilerplate for React Native, which uses Redux and Redux Sagas, is called Ignite Andross. When we decided to go with MST over Redux, the first thing we did was release a new React Native boilerplate called Ignite Bowser. This has been an immensely popular React Native boilerplate, used by companies all over the world. Check it out if you’re interested in how we set up our MST stores!

Additionally, head on over to the MST documentation to learn more:

You can also watch my 25 minute talk at React Amsterdam where I use MST to build a React Native app:

As always, you can hit me up on Twitter with any questions or comments about the article. I’d love to hear your thoughts. If this article helps influence you to try out MST, please let me know.

And —you knew this plug for Infinite Red was coming —if you know anybody who needs help with a React or React Native app (whether Redux, MST, or any other state management system), send them my way (hello@infinite.red). Make sure to let me know you sent them!

Special thanks to Michel Weststrate, the creator of MobX and MST, for reviewing this article for accuracy, as well as Gant Laborde, Mark Rickert, Derek Greenberg, and Kyle Shevlin for their help and review.

Jamon Holmgren is co-founder and CTO of Infinite Red, a mobile app/web design and dev company based in the Portland area & distributed across the USA. He lives in southwest Washington State with his wife and four kids. Follow him on Twitter for more React and React Native discussions, dad jokes, and random pictures of him in goalie gear.

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

Published in Red Shift

The official Infinite Red publication for React Native design & development. We’re a fully distributed team building world-class apps for over 20 years for clients all around the world.

Written by Jamon Holmgren

Co-founder & CTO @infinite_red. Lutheran, husband, dad to 4, React Native Radio podcast host, Twitch streamer, hockey goalie. Talking shop!

Responses (6)

What are your thoughts?

Thanks for this great insight into the current state of Mobx!
Imho, in the world of digital product engineering where speed is equally important as the quality, the 3 points mentioned in the Downsides section would arguably outweigh the other…

A little late to the party, but I wanted to share my thoughts anyway.
I am now beginning a new project, and every time I do that I check whether there is something new I would like to try. Every time I do this, I always return to Mobx State Tree.
It…

Wonderful article on MobX-State-Tree, We are planning to use ignite and that's why I mined for MST resources. This one helps a lot.