MobX and React Integration

MobX and React Integration

·

10 min read

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:

  1. State

  2. Actions

  3. 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)

  1. We will scaffold a react application using create-react-app.

     npx create-react-app react-mobx
     cd react-mobx
    
  2. Next we will install mobx and mobx-react packages to use MobX in our react application. We are also installing the uuid package to generate a unique id for our Todos.

     npm install --save mobx mobx-react uuid
    
  3. Now we will create an Observable store to save our Todos, create a file named store.js under the src/ 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 the Provider 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();
    
  4. We will now create a CreateTodo.js file to hold our CreateTodo 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's useContext() hook.

    Using the helper Provider component offered by MobX, MobX creates the context for our TodoStore.

    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 the onChange method.

    When the form is submitted we are calling the handleSubmit method, and setting the value of the title variable as a todo and clearing it.

  5. 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 the toggleCompleted() method when the todo is checked and removeTodo method to delete to Todo.

  6. 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.

  1. Under the src/ folder create a file named store.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