Mobx React - State Management Alternative to Redux
Mobx React - State Management Alternative to Redux

A lot of people don't like how verbose Redux is. This is why in this video you will learn the basics of the alternative which is called Mobx. And we will build here a small project with Mobx and also compare it with Redux.

Basic project

Here I already prepared for us a small React application.

Initial project

As you can see here we have an input where we can type something and we can click "Add user". After this we add a new user in the end of the user list and our total is being increased.

Here is the code.

const Users = () => {
  const [inputValue, setInputValue] = useState('')
  const [users, setUsers] = useState([])
  const addUser = () => {
    const newUser = {
      id: +Math.random().toFixed(4),
      name: inputValue
    }
    setUsers([...users, newUser])
    setInputValue('')
  }
  return (
    <div>
      <div>Total: {users.length}</div>
      <div>
        <input type="text" value={inputValue} onChange={(e) => setInputValue(e.target.value)}
        <button onClick={addUser}>Add user</button>
      </div>
      <div>
        {users.map((user, index) => (
          <div key={index}>{user.name}</div>
        ))}
      </div>
    </div>
  )
}

We have an array of users and the input value inside the state. When we type something in the input we change our inputValue in state. We also have a function addUser which generated and pushes the user in the array.

This is an implementation of the small feature without using Redux or Mobx.

The goal of this post is to show you how we can move the whole logic of this component inside Mobx and to compare it with Redux at the end.

Mobx installation

First of all let's install Mobx and Mobx-react.

yarn add mobx mobx-react

And it works exactly like Redux. Mobx is the library which is plain Javascript and doesn't know anything about the framework. mobx-react is a library with bindings of Mobx to React.

Planning architecture

Our next step is to plan what we want to move inside Mobx. Here we have state for the inputValue. It doesn't have a lot of sense to move it in the Mobx because it doesn't have any logic and it doesn't have any data.

What we want to move inside is users array and add user functionality. So the idea is to move our users logic to external state which in our case will be Mobx. In this case any components can access this data and update it from anywhere.

Mobx diagram

In order to do that we want to create a store. And just to remind you inside Redux we have just a single global store that we update every single time.

It is not like this inside Mobx. We can create lots of stores. And this is a huge difference between Redux and Mobx.

Adding store

And actually here I want to create a new file which will be a store for our users.

// src/usersStore.js
class UsersStore {
  users = [];
}

export const usersStore = new UsersStore();

Inside we created just a plain class with users data. After this we exported the instance of this class so we can use this instance everywhere.

As you can see here we have zero code about Mobx. What we need to do how is provide the store inside our component.

import { usersStore } from "./usersStore";

const App = () => {
  return (
    <div>
      <h1>Monsterlessons Academy</h1>
      <Users store={usersStore} />
    </div>
  );
};

We imported our usersStore in App component and provided it as a props to our component. This is a typical approach for Mobx.

We have just a component where inside we are providing different store and this component doesn't know what store it uses at all.

Now let's jump back to our class and move addUser function there.

class UsersStore {
  ...
  addUser = (name) => {
    const newUser = {
      id: +Math.random().toFixed(4),
      name,
    };
    this.users.push(newUser);
  }
}

Our addUser function here just gets a name, generates a user and pushes it to array. In comparison to Redux we don't write immutable code here. It is totally fine to use push.

Now we can update our component.

const Users = ({ store }) => {
  ...
  const addUser = () => {
    store.addUser(inputValue);
    setInputValue("");
  };

  return (
    <div>
      ...
      <div>
        {store.users.map((user, index) => (
          <div key={index}>{user.name}</div>
        ))}
      </div>
    </div>
  );
}

Instead of using local state we use properties from the store instance. If we look in browser everything is working as previously.

But actually it is not because of Mobx at all. We didn't write any Mobx code. What we do is just updating the array of data inside the class by reference.

To understand a problem let's write an interval inside App which will change our state.

// src/App.js
setInterval(() => {
  usersStore.addUser('foo')
}, 1000)

We want to use Mobx as an external data storage which we can update from anywhere. This is exactly what we try to do now in App component.

Not working functionality

As you can see our component was not rerendered because we miss Mobx code. First of all we must define what properties inside our class we want to watch. Secondly we must tell Mobx that we want to observe our component are rerender it when needed.

import { action, makeObservable, observable } from "mobx";
class UsersStore {
  users = [];

  constructor() {
    makeObservable(this, {
      users: observable,
      addUser: action,
    });
  }
}

We defined inside a constructor makeObservable function where we can specify what data and functions we want to observe. For data we use observable and for functions (like actions in Redux) we use action.

Rerendering the component

Now we must wrap our Users component in additional function so Mobx can observe it's changes.

import { observer } from "mobx-react";

const Users = observer(({ store }) => {
  ...
}

So we pass the whole component in observer and it means that Mobx must observe our component for changes and rerender when needed.

Working interval

Now every single second we get new user foo because of the setInterval in App component.

Computed

The next thing that I want to show you is computed. We can define some computed properties inside our class.

class UsersStore {
  ...
  constructor() {
    makeObservable(this, {
      users: observable,
      addUser: action,
      total: computed,
    });
  }

  ...

  get total() {
    return this.users.length;
  }
}

We created a new getter total which returns the computed property and defined it in makeObservable so Mobx can watch it correctly.

Now inside our component we can use a computed property.

return (
    <div>
      <div>Total: {store.total}</div>
      ...
    </div>
  );

As you can see it works exactly like before.

Mobx vs Redux

Now I want to sum up the differences between Mobx and Redux

Inside Redux we have just a single state. This state is pure and we never mutate it. This is extremely important as it simplifies debugging, you can rollback your state and it is more scalable.

This is exactly why Redux is still the most popular state management.

Another important point is learning curve. It is much more difficult to master Redux because you have lots of concepts there. You have actions, async action, reducers, selectors and much more. In comparison to Redux, Mobx is much simpler.

And the thing that people don't like about Redux the most is the boilerplate. We are writing lots of code inside Redux and it is fully configurable.

In Redux we don't have anything which happens under the hood like in Mobx. This is exactly why Mobx is much easier to start with but is much harder to tune later.

My preferences

This is exactly why I prefer Redux for my projects. Because it is more popular, I can configure it really deeply and I don't need hidden magic. But if you want to learn something easier or you simply need a fast start then Mobx is a really nice alternative.

And actually if you are interested to learn how to build real React project from start to the end make sure to check my React hooks course.

📚 Source code of what we've done