JavaScript
Article
By Michael Wanyoike

Redux vs MobX: Which Is Best for Your Project?

By Michael Wanyoike

Pieces of data shaped like birds, migrating Redux to MobX

For a lot of JavaScript developers, the biggest complaint with Redux is the amount of boilerplate code needed to implement features. A better alternative is MobX which provides similar functionality but with lesser code to write.

For MobX newbies, take a quick look at this introduction written by MobX’s creator. You can also work through this tutorial to gain some practical experience.

The goal of this article is to help JavaScript developers decide which of these two state management solutions are best for their projects. I’ve migrated this CRUD Redux project to MobX to use as an example in this article. I’ll first discuss the pros and cons of using MobX then I’ll demonstrate actual code samples from both versions to show the difference.

The code for the projects mentioned in this article can be found on GitHub:

What do Redux and MobX have in Common?

First, let’s look at what they both have in common. They:

  • Are open-source libraries
  • Provide client-side state management
  • Support time-travel debugging via redux-devtools-extension
  • Are not tied to a specific framework
  • Have extensive support for React/React Native frameworks

4 Reasons to use MobX

Let’s now look at the main differences between Redux and MobX.

1. Easy to learn and use

For a beginner, you can learn how to use MobX in just 30 minutes. Once you learn the basics, that’s it. You don’t need to learn anything new. With Redux the basics are easy too. However, once you start building more complex applications you will have to deal with:

  • Handling async actions with redux-thunk
  • Simplifying your code with redux-saga
  • Define selectors to handle computed values etc.

With MobX, all these situations are “magically” taken care of. You don’t need additional libraries to handle such situations.

2. Less code to write

To implement a feature in Redux, you need to update at least four artifacts. This includes writing code for reducers, actions, containers and components. This is particularly annoying if you are working on a small project. MobX only requires you to update at least 2 artifacts (i.e. the store and the view component).

3. Full support for object-oriented programming

If you prefer writing object-oriented code, then you will be pleased to know you can use OOP to implement state management logic with MobX. Through the use of decorators such as @observable and @observer, you can easily make your plain JavaScript components and stores reactive. If you prefer functional programming, no problem; that is supported as well. Redux, on the other hand, is heavily geared towards functional programming principles. However, you can use the redux-connect-decorator library if you want a class based approach.

4. Dealing with nested data is easy

In most JavaScript applications, you’ll find yourself working with relational or nested data. To be able to use it in a Redux store, you will have to normalize it first. Next, you have to write some more code to manage tracking of references in normalized data.

In MobX, it is recommended to store your data in a denormalized form. MobX can keep track of the relations for you and will automatically re-render changes. By using domain objects to store your data, you can refer directly to other domain objects defined in other stores. In addition, you can use (@)computed decorators and modifiers for observables to easily solve complex data challenges.

3 Reasons not to use MobX

1. Too much freedom

Redux is a framework that provides strict guidelines on how you write state code. This means you can easily write tests and develop maintainable code. MobX is a library and has no rules on how to implement it. The danger with this is that it’s very easy to take shortcuts and apply quick fixes which can lead to unmaintainable code.

2. Hard to debug

MobX’s internal code “magically” handles a lot of logic to make your application reactive. There’s an invisible area where your data passes between the store and your component which makes it difficult to debug when you have a problem. If you change state directly in components, without using @actions, you will have a hard time pinpointing the source of a bug.

3. There could be a better alternative to MobX

In software development, new emerging trends appear all the time. Within a few short years, current software techniques can quickly loose momentum. At the moment, there are several solutions competing with both Redux and Mobx. A few examples are Relay/Apollo & GraphQL, Alt.js and Jumpsuit. Any of these technologies has the potential to become the most popular. If you really want to know which one is best for you, you’ll have to try them all.

Code Comparison: Redux vs MobX

Enough theory, let’s look at the code. First, we compare how each version does bootstrapping.

Bootstrapping

Redux Version:
In Redux, we first define our store and then we pass it to App via Provider. We’ll also need to define redux-thunk and redux-promise-middleware to handle asynchronous functions. The redux-devtools-extension allows us to debug our store in time-traveling mode.

// src/store.js
import { applyMiddleware, createStore } from "redux";
import thunk from "redux-thunk";
import promise from "redux-promise-middleware";
import { composeWithDevTools } from 'redux-devtools-extension';
import rootReducer from "./reducers";

const middleware = composeWithDevTools(applyMiddleware(promise(), thunk));

export default createStore(rootReducer, middleware);

-------------------------------------------------------------------------------

// src/index.js
....
ReactDOM.render(
  <BrowserRouter>
    <Provider store={store}>
      <App />
    </Provider>
  </BrowserRouter>,
  document.getElementById('root')
);

MobX Version:
In MobX, we need to setup multiple stores. In this case, I am using only one store which I’ve placed in a collection named allStores. A Provider is then used to pass the stores collection to the App.

As mentioned earlier, MobX doesn’t need external libraries to handle async actions, hence the fewer lines. However, we do need the mobx-remotedev to connect to the redux-devtools-extension debugging tool.

// src/stores/index.js
import remotedev from 'mobx-remotedev';
import Store from './store';

const contactConfig = {
  name:'Contact Store',
  global: true,
  onlyActions:true,
  filters: {
    whitelist: /fetch|update|create|Event|entity|entities|handleErrors/
  }
};

const contactStore = new Store('api/contacts');

const allStores = {
  contactStore: remotedev(contactStore, contactConfig)
};

export default allStores;

-------------------------------------------------------------------------------

// src/index.js
...
ReactDOM.render(
  <BrowserRouter>
    <Provider stores={allStores}>
      <App />
    </Provider>
  </BrowserRouter>,
  document.getElementById('root')
);

The amount of code here is roughly about the same in both versions. MobX has fewer import statements though.

Props injection

Redux Version:
In Redux, state and actions are passed to props using react-redux’s connect() function.

// src/pages/contact-form-page.js
...
  // accessing props
  <ContactForm
    contact={this.props.contact}
    loading={this.props.loading}
    onSubmit={this.submit}
  />
...

// function for injecting state into props
function mapStateToProps(state) {
  return {
    contact: state.contactStore.contact,
    errors: state.contactStore.errors
  }
}

// injecting both state and actions into props
export default connect(mapStateToProps, { newContact,
  saveContact,
  fetchContact,
  updateContact
})(ContactFormPage);

MobX Version:
In MobX, we simply inject the stores collection. We use @inject at the top of a container or component class to do this. This makes stores available in props which in turn allows us to access a specific store and pass it to a child component. Both state and actions are accessed via properties in the store object hence no need to pass them separately as with the case in Redux.

// src/pages/contact-form-page.js

...
@inject("stores") @observer // injecting store into props
class ContactFormPage extends Component {
...
  // accessing store via props
  const { contactStore:store } = this.props.stores;
  return (
      <ContactForm
        store={store}
        form={this.form}
        contact={store.entity}
      />
  )
...
}

The MobX version seems to be easier to read. However, we can use redux-connect-decorators to simplify Redux code. In that case there will be no clear winner.

Defining stores, actions, and reducers

To keep this article lean, I’ll show you a code sample for just one action.

Redux Version:
In Redux, we need to define actions and reducers.

// src/actions/contact-actions.js
...
export function fetchContacts(){
  return dispatch => {
    dispatch({
      type: 'FETCH_CONTACTS',
      payload: client.get(url)
    })
  }
}
...

// src/reducers/contact-reducer
...
switch (action.type) {
    case 'FETCH_CONTACTS_FULFILLED': {
      return {
        ...state,
        contacts: action.payload.data.data || action.payload.data,
        loading: false,
        errors: {}
      }
    }

    case 'FETCH_CONTACTS_PENDING': {
      return {
        ...state,
        loading: true,
        errors: {}
      }
    }

    case 'FETCH_CONTACTS_REJECTED': {
      return {
        ...state,
        loading: false,
        errors: { global: action.payload.message }
      }
    }
}
...

MobX Version:
In MobX, the logic for the action and the reducer is done in one class. I’ve defined an async action that calls another action entities fetched after response has been received.

Since MobX uses the OOP style, the Store class defined here has been refactored to allow easy creation of multiple stores using the class constructor. Hence the code demonstrated here is base code that is not tied to a particular domain store.

// src/stores/store.js
...
@action
fetchAll = async() => {
  this.loading = true;
  this.errors = {};
  try {
    const response = await this.service.find({})
    runInAction('entities fetched', () => {
      this.entities = response.data;
      this.loading = false;
    });
  } catch(err) {
      this.handleErrors(err);
  }
}
...

Believe it or not, the logic defined in both versions do the same tasks, which are:

  • Update the UI loading state
  • Fetch data asynchronously
  • Catch exceptions and update state

In Redux, we have used 33 lines of code. In MobX, we have used about 14 lines of code to achieve the same result! A major benefit of the MobX version is that you can reuse the base code in almost all the domain store classes with little or no modification. That means you can build your application faster.

Other differences

To create forms in Redux, I’ve used redux-form. In MobX, I’ve used mobx-react-form. Both libraries are mature and help you handle form logic easily. Personally, I prefer mobx-react-form since it allows you to validate fields via plugins. With redux-form, you either write your own validation code or you can import a validation package to handle validation for you.

One tiny downside with MobX is that you can’t directly access certain functions in observable objects since they are not really plain JavaScript objects. Luckily, they have provided the function toJS() which you can use to convert observable objects to plain JavaScript objects.

Conclusion

Clearly, you can see that MobX’s code base is far much leaner. Using OOP style and good development practices, you can rapidly build applications. The major downside is that it’s very easy to write poor, unmaintainable code.

Redux, on the other hand, is more popular and well suited for building large and complex projects. It is a strict framework with safeguards ensuring every developer writes code that is easy to test and maintain. However, it is not well suited to small projects.

Despite MobX’s drawbacks, you can still build large projects if you follow good practices. In the words of Albert Einstein, “Make everything simple as possible, but not simpler”.

I hope I have provided enough information to make a clear case whether to migrate to MobX or stick with Redux. Ultimately, the decision depends on the type of project you are working on, and the resources available to you.

If you need any clarifications on the MobX example code, feel free to ask questions in the comments below. Have I convinced you to give MobX a shot in your next React project?

This article was peer reviewed by Dominic Myers and Vildan Softic. Thanks to all of SitePoint’s peer reviewers for making SitePoint content the best it can be!

More:
  • Mark Erikson

    For what it’s worth, there’s no need to use that linked `redux-connect-decorators` library. The React-Redux `connect` function _can_ already be used as a decorator, and also allows passing an object full of action creators as the second argument to `connect` instead of an actual `mapDispatch` function. So, that library appears to provide no additional functionality.

    That said, the Redux team generally recommends against using `connect` as a decorator for a couple reasons. The decorator syntax and Babel implementation are still unstable, and using `connect` as a decorator can sometimes lead to unexpected behavior with things like PropTypes (as they end up on the wrapper component, not the wrapped component).

    (Source: I’m a Redux maintainer.)

  • I’ve only used MobX a little (and a while ago), but one big disadvantage I found was that the observable/mutating data approach is fundamentally at odds with “PureComponents”, which require immutable properties to update force a re-render.

Recommended
Sponsors
Get the latest in JavaScript, once a week, for free.