State management in React with MobX
Tags: javascript, webdev, react, mobx
Published: 2022-05-12 21:22:44


by [Ejiro Thankgod](https://blog.openreplay.com/authors/ejiro-thankgod)

According to the [Documentation](https://mobx.js.org/README.html),

> MobX is a battle-tested library that makes state management simple and scalable by transparently applying functional reactive programming (TFRP).

One of the most asked questions between developers in creating modern React applications includes state management. In this tutorial, we will learn how to use MobX as a state management library for React applications. We'll use it to manage state, which will help us understand the concept and guidelines of using MobX.

## What is MobX?

Like other similar libraries (e.g., Redux, Recoil, Hook states), MobX is a state manager, but with simplicity and scalability when it comes to state management.

Mobx distinguishes between the following concepts in an application.

- State
- Actions
- Derivations

**State**
The State is the data that manages your applications. It contains different data types ranging from arrays, strings, numbers, and objects that MobX allows you to work with. All you have to do is make sure that all properties you want to change over time are `observable` so MobX can track them. Below is a simple example.

```javascript
import React from "react";
import ReactDOM from "react-dom";
import { makeAutoObservable } from "mobx";
import { observer } from "mobx-react";

// Model the application state.
class Timer {
  secondsPassed = 0;

  constructor() {
    makeAutoObservable(this);
  }

  increase() {
    this.secondsPassed += 1;
  }

  reset() {
    this.secondsPassed = 0;
  }
}

const myTimer = new Timer();

// Build a "user interface" that uses the observable state.
const TimerView = observer(({ timer }) => (
  <button onClick={() => timer.reset()}>
    Seconds passed: {timer.secondsPassed}
  </button>
));

ReactDOM.render(<TimerView timer={myTimer} />, document.body);

// Update the 'Seconds passed: X' text every second.
setInterval(() => {
  myTimer.increase();
}, 1000);
```

The `TimeView` React component wrapped around the `observer` will automatically detect that rendering depends on the `timer.secondsPassed` observable, even though this relationship is not defined explicitly.

Every event (`onClick`/`setInterval`) invokes an action (`myTimer.increase`/`myTimer.reset`) that updates observable state (`myTimer.secondsPassed`). Changes in the observable state are propagated precisely to all computations and effects (`TimeView`) that depend on the changes made.

**Action**
If a state is your data, then an Action is any block of code that can change such data: User events, Backend data, etc. An action is like a person who alters data in the spreadsheet cell. In the `Timer` code above, we can see `increase`  and `reset` methods that change the value of `secondsPassed`. Actions help you structure your code block and prevent you from constantly changing state when you don't have to. Methods that modify the state are called `actions` in MobX. 

**Derivations**
Whatever that is gotten from the state is known as derivation, and it exists in different forms, but we'll be looking at the different kinds of derivations of MobX:

- Computed Values
- Reactions

**Computed Values**
These are values that can be derived from the state using a pure function. They will be updated automatically by MobX, and they are suspended when not in use. Below is an example of a Computed Value.

```javascript
class TodoList {
  @observable todos = [];
  @computed get unfinishedTodoCount() {
    return this.todos.filter((todo) => !todo.finished).length;
  }
}
```

**Reactions**
Reactions are like computed values: they react to state changes but instead produce side effects. In React, you can turn stateless function components into reactive components by simply adding the observer function. `Observer` converts React function components into a derivation of data they render. MobX makes sure the components are always re-rendered when needed but not more than that. Below is an example of how an `Observer` function can be used:

```javascript
const Todos = observer(({ todos }) => (
  <ul>
    {todos.map((todo) => (
      <Todoview ... />
    ))}
  </ul>
));
```

Custom Reactions can be created using `autorun`, `reaction`, or `when`.

```javascript
//autorun//
autorun(() => {
  console.log("Tasks left: " + todos.unfinishedTodoCount);
});

//reaction//
const reaction = reaction(
  () => todos.map((todo) => todo.title),
  (titles) => console.log("reaction:", titles.join(", "))
);

//when//
async function x() {
  await when(() => that.isVisible);
  // etc...
}
```

MobX can be installed using any package manager such as npm by using the `npm install -- save mobx` command.

## Why should you consider MobX?

The main goal of MobX is to improve state management for developers and create an easier way of managing Javascript application states with less code and boilerplates. MobX uses observable data, which helps track changes automatically, making life easier for developers.

MobX allows you to manage your application state outside of any framework. This makes code decoupled, portable, and easily testable, which is why it's termed **UNOPINIONATED.**


## MobX vs. Redux/Recoil/HookState

Unlike other state managers like Redux and Easy Peasy, MobX uses multiple stores to handle the application state. You can separate the stores so all application states will be in a single store like Redux.

One of the top issues about Redux is the amount of boilerplate code that comes with it, and integration with React leads to excess boilerplates, which developers find unappealing. MobX requires little or no boilerplate code and does not require any special tools, making its setup simple and easy.

Redux is more opinionated, leading to pure functions, but MobX wins due to its unopinionated feature when it comes to scalability.

## When to use MobX?

Although some developers tend to get confused about when to use which state management library for which project, let's break it down to understand. If you want to write minimal code, with little or no boilerplate obstructing your view, or if you are trying to update a record field without any special tools, then MobX is what you should be thinking of.


## Open Source Session Replay

_[OpenReplay](https://github.com/openreplay/openreplay) is an open-source alternative to FullStory and LogRocket. It gives you full observability by replaying everything your users do on your app and showing how your stack behaves for every issue. OpenReplay is self-hosted for full control over your data._

![replayer.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1647097041609/Qi6sMOaaB.png)

_Happy debugging for modern frontend teams - [start monitoring your web app for free](https://github.com/openreplay/openreplay)._



## Building a React application with MobX.

We'll be building a Todo Application for note keeping, and beautifying our Todo App with Framer-Motion library. We'll be using Mobx as a state manager in our Todo App. We'll create a store that will have a class, and that class will have a data constructor that will make the class observable to changes in state.

First, we'll set up our environment by creating our react application with the following command on your terminal.

`npx create-react-app todo-app --template typescript`

Then we change our directory and install the needed dependencies before creating our components and state.

```javascript
cd todo-app
npm install -s mobx mobx-react-lite
npm install framer-motion
npm install react-icons
npm start
```

## Create a Store Component

We'll be creating our `store.ts` component in our root folder, and we are using Mobx with React Context API to make our store available to all the components.

```javascript
//store.ts//

import { createContext, useContext } from "react";
import todoStore from "./store/TodoStore";

const store = {
  todoStore: todoStore(),
};

export const StoreContext = createContext(store);

export const useStore = () => {
  return useContext<typeof store>(StoreContext);
};

export default store;
```

## Create a TodoStore Component

The `TodoStore.ts` carries our state component. First, we create a function `todoStore`, which returns `makeAutoObservable` (from MobX) with a list with a title and id.

```javascript
//TodoStore.ts//

import { makeAutoObservable } from "mobx";

const todoStore = () => {
  return makeAutoObservable({
    list: [] as { title: string; id: number }[],
  });
};

export default todoStore;
```

## Create a TodoForm Component

We'll have to build a `TodoForm.tsx` component for Creating Todos.

```javascript
//TodoForm.tsx//

import { motion } from "framer-motion";
import { GoPlus } from "react-icons/go";
import { action } from "mobx";
import { FormEvent } from "react";
import { useStore } from "../stores";

const TodoForm = () => {
  const { todoStore } = useStore();
  const handleSubmit = action((e: FormEvent) => {
    e.preventDefault();
    const formData = new FormData(e.target as HTMLFormElement);
    const value = formData.get("title")?.toString() || "";
    todoStore.list.push({
      title: value,
      id: Date.now(),
    });
  });

  return (
    <form className="addTodos" action="#" onSubmit={handleSubmit}>
      <input name="title" placeholder="add text" className="todo-input" />

      <motion.button
        whileHover={{ scale: 1.1 }}
        whileTap={{ scale: 0.9 }}
        className="add-btn"
      >
        <GoPlus />
      </motion.button>
    </form>
  );
};

export default TodoForm;
```
    
    
## Create a TodoList Component

To list all our input Todo's, we'll have to create a `TodoList.tsx` component.

```javascript
//TodoList.tsx//

import { AnimatePresence } from "framer-motion";
import { observer } from "mobx-react-lite";
import { useStore } from "../stores";
import { motion } from "framer-motion";

const TodoList = () => {
  const { todoStore } = useStore();

  return (
    <motion.li
      whileHover={{
        scale: 0.9,
        transition: { type: "spring", duration: 0.2 },
      }}
      exit={{
        x: "-60vw",
        scale: [1, 0],
        transition: { duration: 0.5 },
        backgroundColor: "rgba(255,0,0,1)",
      }}
      className="displaytodos"
    >
      {todoStore.list.map((l) => (
        <h3 className="card" key={l.id}>
          {l.title}
        </h3>
      ))}
    </motion.li>
  );
};

export default observer(TodoList);
```   
    
## Create TodoDetails Component

The `TodoDetails.tsx` file has little:  our `TodoForm` and `TodoList` components.

```javascript
//TodoDetails.tsx//

import React from "react";
import TodoForm from "./TodoForm";
import TodoList from "./TodoList";

function TodoOverview() {
  return (
    <>
      <TodoForm />
      <TodoList />
    </>
  );
}

export default TodoOverview;
```

    
## Create Main.css Component

The main styling is as follows.

```
@import url("https://fonts.googleapis.com/css2?family=RocknRoll+One&display=swap");

html {
  line-height: 1.15;
}

* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
  font-family: "RocknRoll One", sans-serif;
}

body {
  background: linear-gradient(
    190deg,
    rgb(134, 123, 205) 0%,
    rgb(106, 90, 171) 100%
  );
  background-repeat: no-repeat;
  background-size: cover;
  background-attachment: fixed;
  color: #222;
  overflow: hidden;
}

.App {
  margin-top: 3rem;
  display: flex;
  flex-direction: column;
}

.App h1 {
  display: inline;
  text-align: center;
  margin-bottom: 2rem;
  color: #e1ebfd;
  text-shadow: 0 0 5px #433aa8, 3px -1px 5px #271c6c;
}

.addTodos {
  display: flex;
  justify-content: center;
}

.todo-input {
  min-width: 15rem;
  width: 40vw;
  max-height: 2.5rem;
  background-color: #e1ebfd;
  border: none;
  border-radius: 5px;
  padding: 0.5rem 1rem;
  align-self: center;
}

.todo-input:focus {
  outline: none;
  border: 2px solid rgb(67, 58, 168);
}

.add-btn {
  margin-left: 1rem;
  background-color: #271c6c;
  color: #e1ebfd;
  border-radius: 50%;
  border: 2px solid #e1ebfd;
  font-size: 1.5rem;
  width: 3.2rem;
  height: 3.2rem;
  cursor: pointer;
  box-shadow: 2px 4px 10px #271c6c;
  display: flex;
  justify-content: center;
  align-items: center;
}

.add-btn:focus {
  outline: none;
}

.displaytodos {
  margin-top: 3rem;
  display: flex;
  flex-direction: column;
  align-items: center;
}

.card {
  display: flex;
  flex-direction: column;
  text-align: center;
  background: rgb(180, 182, 218);
  background: radial-gradient(
    circle,
    hsla(237, 34%, 78%, 0.9) 0%,
    hsla(219, 88%, 94%, 0.9) 100%
  );
  margin: 0 1rem 1rem 0;
  height: 4rem;
  width: 18rem;
  border-radius: 0.5rem;
  padding: 1rem;
  position: relative;
}

@media Screen and (max-width: 640px) {
  .displaytodos {
    overflow: hidden;
    margin-top: 2rem;
  }
  .displaytodos ul {
    display: flex;
    flex-direction: column;
    align-items: center;
    margin-left: 0;
    align-self: center;
  }
  .card {
    margin-right: 0;
  }
}
```

## Implementing Framer-Motion

Implementation of Framer-Motion (for animations controlled by motion components ) in the `App.tsx` needs this code.

```javascript
import React from "react";
import TodoDetails from "./components/TodoDetails";
import "./css/main.css";
import { motion } from "framer-motion";

function App() {
  return (
    <div className="App">
      <motion.h1
        initial={{ y: -200 }}
        animate={{ y: 0 }}
        transition={{ type: "spring", duration: 0.5 }}
        whileHover={{ scale: 1.1 }}
      >
        Todo App
      </motion.h1>
      <motion.div
        initial={{ y: 1000 }}
        animate={{ y: 0 }}
        transition={{ type: "spring", duration: 1 }}
      >
        <TodoDetails />
      </motion.div>
    </div>
  );
}

export default App;
```

![Our complete app](https://blog.openreplay.com/static/f4b5b0cc652f76d194f8cd6e2002f2c9/1a152/image01.png)


*And our Todo App seems to be working very fine, handling its inner state with MobX.*

## Summary

In this article, we took a tour of MobX as a React State Management library. We also learned how to use the reactive state of MobX to manage the state of an application, which was quite interesting. We integrated it with our code and Framer-Motion for animations.

## Resources

The Github Repo for our Todo App can be found [here](https://github.com/EJINEROZ/Todo-App), and it's deployed on [Vercel](https://todo-app-beige-two.vercel.app/).


- [MobX](https://mobx.js.org/README.html)
- [MobX Github Page](https://github.com/mobxjs/mobx)
- [MobX-React](https://mobx.js.org/react-integration.html)
- [Intro to MobX](https://www.youtube.com/watch?v=WQQq1QbYlAw)
- [Framer-Motion](https://www.framer.com/developers/)

[![newsletter](https://blog.openreplay.com/static/e6b18eda7a327511296a2f27692f9c70/e9957/newsletter.png)](https://newsletter.openreplay.com/)