In this blog post, we will look at what is MobX, and how MobX can be used in React applications.
We will also look at a simple example using MobX and also build some complex examples using MobX.
What is MobX?
MobX is a state management library, it allows transparent state management and can be used when you require complex state management in your React applications.
It can be used with any framework and even in plain JavaScript applications to create an observable state, it is an un-opinioned framework that allows you to create decoupled code.
It offers helpers and higher-order observer
component to easily integrate with React and can be used as an alternative to redux.
Using Dead Simple Chat JavaScript Chat API and SDK you can add Chat to any React or Web application in minutes. It is a highly scaleable Chat platform.
Why use MobX with React?
Typically when you create React application you use the useState
hook provided by React.
And you should use the useState
hook unless your application grows very big and you require complex state management.
In that case, you could investigate some advanced state management tools that are scaleable, one of those tools is Mobx.
Basically, tools like MobX allow you to automatically update the UI automatically when the underlying state is updated.
To rephrase it, it handles automatic updation of the UI when the variables that affect are UI updated.
Key Mobx Concepts
The MobX has 3 core concepts:
State
Actions
Derivations
1. The State:
The official MobX documents encapsulate it nicely, the "State is the data that drives your application."
The state stores any data that is required by your application, it can be a single variable, plain object or complex data structure.
// store.js
import { makeAutoObservable } from 'mobx';
class CounterStore {
count = 0; // The State
constructor() {
makeAutoObservable(this);
}
increment() {
this.count++;
}
decrement() {
this.count--;
}
}
export const counterStore = new CounterStore();
To demonstrate the state we have created a class called as the CounterStore
. The CounterStore
acts as a very simple data store for our application.
In our CounterStore
the count
variable is our State.
In the constructor, we called a helper method from Mobx called makeAutoObservable(this)
and passed it this
so that it can keep track of the changes in our CounterStore
datastore.
2. Actions
Anything that modifies or updates the state are called as actions. The actions help you structure the code and prevent the inadvertently changing of the state.
Actions also help you keep track of the changes to the state. The state should never be updated directly, and actions should be created to update the state.
In our CounterStore
example the method increment()
and decrement()
are the Actions.
Because the methods increment()
and decrement()
are updating our state variable count
.
// store.js
import { makeAutoObservable } from 'mobx';
class CounterStore {
count = 0; // The State
constructor() {
makeAutoObservable(this);
}
increment() { // Action
this.count++;
}
decrement() { // Action
this.count--;
}
}
export const counterStore = new CounterStore();
3. Derivations
Anything that can be derived from the state is called a Derivation.
For e.g in our CounterStore
if we are using the count
state somewhere in our application, when the count
value is updated the UI displaying the count should also update.
MobX provides us with automatic derivations, with the help of the makeAutoObservable()
method that we have seen before and observer
Higher order component that we will see next.
// App.js
import React from "react";
import { observer } from "mobx-react-lite";
import { counterStore } from "./store";
const App = observer(() => {
return (
<div>
<h1>Counter App using React+MobX</h1>
<p>Count: {counterStore.count}</p>
<button
onClick={() => {
counterStore.increment();
}}
>
Increment
</button>
<button
onClick={() => {
counterStore.decrement();
}}
>
Decrement
</button>
</div>
);
});
export default App;
As you can see in the code sample above, as the state is update the UI is also updated automatically.
Here is the complete example running in CodeSandbox:
Advanced Example using MobX
We will now look at an advanced example using Mobx in this example we will create a Todo application that uses React Context Provider which is provided by MobX to inject state into the components.
If you want a tutorial on how React Context works you refer to our React Context Guide. (Even if you don't know how React Context works you can still follow the tutorial)
We will scaffold a react application using
create-react-app
.npx create-react-app react-mobx cd react-mobx
Next we will install
mobx
andmobx-react
packages to use MobX in our react application. We are also installing theuuid
package to generate a unique id for our Todos.npm install --save mobx mobx-react uuid
Now we will create an Observable store to save our Todos, create a file named
store.js
under thesrc/
folder.// src/store.js import { v4 as uuidv4 } from "uuid"; import { makeAutoObservable } from "mobx"; import { createContext } from "react"; class Todo { id = uuidv4(); title = ""; completed = false; constructor(title) { makeAutoObservable(this); this.title = title; } toggleCompleted() { this.completed = !this.completed; } } class TodoStore { todos = []; constructor() { makeAutoObservable(this); } addTodo(title) { this.todos.push(new Todo(title)); } removeTodo(id) { this.todos = this.todos.filter((todo) => todo.id !== id); } } export const todoStore = new TodoStore(); export const TodoStoreContext = createContext(todoStore);
Update your
index.js
file to include the store and theProvider
from Mobx, and wrap the<Provider ..
around our<App />
component.// src/index.js import React from "react"; import ReactDOM from "react-dom/client"; import { Provider } from "mobx-react"; import { todoStore } from "./store"; import "./index.css"; import App from "./App"; import reportWebVitals from "./reportWebVitals"; const root = ReactDOM.createRoot(document.getElementById("root")); root.render( <React.StrictMode> <Provider todoStore={todoStore}> <App /> </Provider> </React.StrictMode> ); // If you want to start measuring performance in your app, pass a function // to log results (for example: reportWebVitals(console.log)) // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals reportWebVitals();
We will now create a
CreateTodo.js
file to hold ourCreateTodo
component. This component will allow us to add Todos.// src/CreateTodo.js import { useState, useContext } from "react"; import { TodoStoreContext } from "./store"; import { observer } from "mobx-react"; const CreateTodo = observer(() => { const [title, setTitle] = useState(""); const store = useContext(TodoStoreContext); const handleSubmit = (e) => { e.preventDefault(); if (title.trim()) { store.addTodo(title.trim()); setTitle(""); } }; return ( <form onSubmit={handleSubmit}> <input type="text" placeholder="Add todo" value={title} onChange={(e) => setTitle(e.target.value)} /> <button type="submit">Add</button> </form> ); }); export default CreateTodo;
We have created a
CreateTodo
component, this component connects to our store using React'suseContext()
hook.Using the helper
Provider
component offered by MobX, MobX creates the context for ourTodoStore
.Which we have used in our
CreateTodo
component to interact with the store.The rest of the code is very straightforward, there is a local state variable called as
title
which we are updating each time the input text box is updated using theonChange
method.When the form is submitted we are calling the
handleSubmit
method, and setting the value of thetitle
variable as a todo and clearing it.Next, we will create a
TodoItem.js
file, this will hold our TodoItem component, the TodoItem component will show a single todo on the screen, and would also allow the user to toggle the todo.import { useContext } from "react"; import { TodoStoreContext } from "./store"; import { observer } from "mobx-react-lite"; const TodoItem = observer(({ todo }) => { const store = useContext(TodoStoreContext); return ( <li> <input type="checkbox" checked={todo.completed} onChange={() => todo.toggleCompleted()} /> <span>{todo.title}</span> <button onClick={() => store.removeTodo(todo.id)}>Remove</button> </li> ); }); export default TodoItem;
In our code example above, we are importing the store using the
useContext
and calling thetoggleCompleted()
method when the todo is checked andremoveTodo
method to delete to Todo.Finally, we will update our
App.js
file to display the list of the Todos, and import our<CreateTodo />
component.We will fetch all the Todos from the store and iterate over them and will add
TodoItem
component to display the Todo.// src/App.js import { useContext } from "react"; import { observer } from "mobx-react"; import CreateTodo from "./CreateTodo"; import TodoItem from "./TodoItem"; import { TodoStoreContext } from "./store"; const App = observer(() => { const store = useContext(TodoStoreContext); return ( <div style={{ margin: "20px" }}> <h1>Todo App using MobX+React</h1> <CreateTodo /> <ul> {store.todos.map((todo) => ( <TodoItem key={todo.id} todo={todo} /> ))} </ul> </div> ); }); export default App;
As you can see in the live code sandbox below, when the item in the store is Added, Updated to Delete the UI automatically updates.
We are using 3 components, App, CreateTodo and TodoItem and all the components are accurately reflecting the state information and are getting updated each time the Todo updates.
Another Example of using MobX: Fetching data via HTTP
In this example, we will create a PostStore
that stores a list of posts. The List of Posts is retrieved from an API.
We will use the jsonplaceholder.typicode.com/posts
API to fetch a mock list of Posts and save them in our store.
In the store, we will create a method to fetch the posts, which can be called from the component using the store.
- Under the
src/
folder create a file namedstore.js
this will contain our PostStore and our PostStoreContext.
// src/store.js
import { makeAutoObservable } from "mobx";
import { createContext } from "react";
class PostStore {
posts = [];
loading = false;
constructor() {
makeAutoObservable(this);
}
async fetchPosts() {
this.loading = true;
try {
const response = await fetch(
"https://jsonplaceholder.typicode.com/posts"
);
const data = await response.json();
this.posts = data;
} catch (error) {
console.error("Error fetching posts:", error);
} finally {
this.loading = false;
}
}
}
export const postStore = new PostStore();
export const PostStoreContext = createContext(postStore);
In the above code, we have created a fetchPosts()
method to fetch a list of users, and we are saving them in posts
variable.
2. Now let's update index.js
file and wrap our <App />
component in a provider, this will allow us to call useContext
inside the child components to access our store.
// src/index.js
import React from "react";
import ReactDOM from "react-dom";
import { PostStoreContext, postStore } from "./store";
import App from "./App";
ReactDOM.render(
<React.StrictMode>
<PostStoreContext.Provider value={postStore}>
<App />
</PostStoreContext.Provider>
</React.StrictMode>,
document.getElementById("root")
);
3. Next we will create a PostList component that will display a list of posts, create a file named PostList.js
that will hold our PostLists
component
// src/PostList.js
import { useContext } from "react";
import { observer } from "mobx-react-lite";
import { PostStoreContext } from "./store";
const PostList = observer(() => {
const store = useContext(PostStoreContext);
if (store.loading) {
return <div>Loading posts...</div>;
}
return (
<ul>
{store.posts.map((post) => (
<li key={post.id}>
<h3>{post.title}</h3>
<p>{post.body}</p>
</li>
))}
</ul>
);
});
export default PostList;
Here in the PostList
component we are calling useContext
on the PostStoreContext
to fetch the list of posts from the store and displaying them in a list.
4. Finally we will update our <App />
component. In the App component as will use the PostStoreContext
and create a button to call the fetchPosts()
method.
Calling the fetchPosts()
method will trigger and API call, and as the response is received from the API, the UI will be automatically updated with a list of new posts.
// src/App.js
import React, { useContext } from "react";
import { observer } from "mobx-react-lite";
import PostList from "./PostList";
import { PostStoreContext } from "./store";
const App = observer(() => {
const store = useContext(PostStoreContext);
const handleFetchPosts = () => {
store.fetchPosts();
};
return (
<div>
<h1>Posts powered by Mobx</h1>
<button onClick={handleFetchPosts}>Fetch Posts</button>
<PostList />
</div>
);
});
export default App;
You can try out the complete code in the codesanbox below:
JavaScript Chat API and SDK from Dead Simple Chat allows you to add Chat to your React Application is minutes. The Dead Simple Chat is very verstaile chat plaform that can be customised for any use-case.
Key Takeaways
MobX is a state management library. It can be used with any framework but offers helper methods to use with React using
react-mobx
package.MobX should be used when complex state management is required if you want to avoid prop drilling and in cases where you require a global shared state among multiple components.
You should first try using
useState
and passing data using props, or using Higher Order Components if it gets too cumbersome then only you should use a state management library like MobX.MobX allows the management of the complicated state, but its use in the application should be carefully planned, otherwise, it might result in tightly coupled components.
In this guide, we have learned what MobX is and looked at some basic as well as complex examples using MobX.
This article was originally published on the DeadSimpleChat blog : MobX and React Integration