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.
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.
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.
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.
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.
Want to conquer your next JavaScript interview? Download my FREE PDF - Pass Your JS Interview with Confidence and start preparing for success today!
📚 Source code of what we've done