MobX with React and TypeScript
Tags: javascript, mobx, react, typescript
Published: 2020-01-05 11:28:49

MobX is one of the popular state management libraries. One of the great things about MobX is that we can store state in a simple data structure and allow the library to take care of keeping everything up to date. The MobX API is pretty simple; in fact, it has these four simple building blocks at its core:

- Observable
- Actions
- Computed
- Reactions

## Observable

The idea is that when the data changes, the observable object notifies the observers. To define a property as observable, all we need to do is to use `@observable` decorator:

```ts
class TodoStore {
  @observable todos: Todo[]
}
```

Now When a new value is assigned to `todos` array, the notifications will fire, and all the associated observers will be notified.

## Actions

Action is a way to change an observable (update the state). To define an action, we decorate methods inside the store with `@action`:

```ts
@action toggleTodo = (id: string) => {
    this.todos = this.todos.map(todo => {
      if (todo.id === id) {
        return {
          ...todo,
          completed: !todo.completed
        };
      }
      return todo;
    });
};
```

## Computed

Computed can be used to derive values from the existing state or other computed values:

```ts
@computed get info() {
    return {
      total: this.todos.length,
      completed: this.todos.filter(todo => todo.completed).length,
      notCompleted: this.todos.filter(todo => !todo.completed).length
    };
}
```

## Reactions

Reactions track observables from inside the store itself. In the example below if the action to set `todos` is called, then it runs the second argument.

```ts
class TodoStore {
  constructor() {
    reaction(
      () => this.todos,
      _ => console.log(this.todos.length)
    );
  }
```

# Creating a Simple Todo App with MobX and React

Now that we have talked about the main concepts, let's create a simple todo app using React, MobX and TypeScript:

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/dkkqq7tht0qa1yylh09t.png)

So go to the terminal, create a directory then CD into it then type in this command to scaffold a new React project:

```ts
npx create-react-app . --typescript
```

For this project, I am using Bootstrap so let's add it as a dependency to the newly created project:

```ts
yarn add bootstrap --save
```

Now go to `index.tsx` and import `bootstrap.css`:

```ts
import "bootstrap/dist/css/bootstrap.css"
```

Now we'll install the needed dependencies:

```ts
yarn add mobx mobx-react-lite uuid @types/uuid --save
```


The next thing we have to do is to set `experimentalDecorators` flag to `true` in `tsconfig.json` in order for the MobX decorators to compile properly:

```json
{
  "compilerOptions": {
    // other stuff...

    "experimentalDecorators": true
  }
}
```

After all the above stuff is done, we have MobX ready to go. Now let's create a store inside the project `src/stores/TodoStore.ts`. Add the following code to it:

```ts
import { observable, action, computed, reaction } from "mobx"
import { createContext } from "react"
import uuidv4 from "uuid/v4"

export interface Todo {
  id?: string;
  title: string;
  completed: boolean;
}

class TodoStore {
  constructor() {
    reaction(() => this.todos, _ => console.log(this.todos.length))
  }

  @observable todos: Todo[] = [
    { id: uuidv4(), title: "Item #1", completed: false },
    { id: uuidv4(), title: "Item #2", completed: false },
    { id: uuidv4(), title: "Item #3", completed: false },
    { id: uuidv4(), title: "Item #4", completed: false },
    { id: uuidv4(), title: "Item #5", completed: true },
    { id: uuidv4(), title: "Item #6", completed: false },
  ]

  @action addTodo = (todo: Todo) => {
    this.todos.push({ ...todo, id: uuidv4() })
  }

  @action toggleTodo = (id: string) => {
    this.todos = this.todos.map(todo => {
      if (todo.id === id) {
        return {
          ...todo,
          completed: !todo.completed,
        }
      }
      return todo
    })
  }

  @action removeTodo = (id: string) => {
    this.todos = this.todos.filter(todo => todo.id !== id)
  }

  @computed get info() {
    return {
      total: this.todos.length,
      completed: this.todos.filter(todo => todo.completed).length,
      notCompleted: this.todos.filter(todo => !todo.completed).length,
    }
  }
}

export default createContext(new TodoStore())
```

Now create a new folder called components in the `src` directory and add `TodoAdd.tsx` and `TodoList.tsx`.

### TodoAdd

```tsx
import React, { useContext, useState } from "react"
import TodoStore from "../stores/TodoStore"
import { observer } from "mobx-react-lite"

const AddTodo = () => {
  const [title, setTitle] = useState("")
  const todoStore = useContext(TodoStore)
  const { addTodo, info } = todoStore

  return (
    <>
      <div className="alert alert-primary">
        <div className="d-inline col-4">
          Total items: &nbsp;
          <span className="badge badge-info">{info.total}</span>
        </div>
        <div className="d-inline col-4">
          Finished items: &nbsp;
          <span className="badge badge-info">{info.completed}</span>
        </div>
        <div className="d-inline col-4">
          Unfinished items: &nbsp;
          <span className="badge badge-info">{info.notCompleted}</span>
        </div>
      </div>
      <div className="form-group">
        <input
          className="form-control"
          type="text"
          value={title}
          placeholder="Todo title..."
          onChange={e => setTitle(e.target.value)}
        />
      </div>
      <div className="form-group">
        <button
          className="btn btn-primary"
          onClick={_ => {
            addTodo({
              title: title,
              completed: false,
            })
            setTitle("")
          }}
        >
          Add Todo
        </button>
      </div>
    </>
  )
}

export default observer(AddTodo)
```

### TodoList

```tsx
import React, { useContext } from "react";
import TodoStore from "../stores/TodoStore";
import { observer } from "mobx-react-lite";

const TodoList = () => {
  const todoStore = useContext(TodoStore);
  const { todos, toggleTodo, removeTodo } = todoStore;
  return (
    <>
      <div className="row">
        <table className="table table-hover">
          <thead className="thead-light">
            <tr>
              <th>Title</th>
              <th>Completed?</th>
              <th>Actions</th>
            </tr>
          </thead>
          <tbody>
            {todos.map(todo => (
              <tr key={todo.id}>
                <td>{todo.title}</td>
                <td>{todo.completed ? "✅" : ""}</td>
                <td>
                  <button
                    className="btn btn-sm btn-info"
                    onClick={_ => toggleTodo(todo.id!)}
                  >
                    Toggle
                  </button>
                  <button
                    className="btn btn-sm btn-danger"
                    onClick={_ => removeTodo(todo.id!)}
                  >
                    Remove
                  </button>
                </td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    </>
  );
};

export default observer(TodoList);

```

Both components use `observer` which is an [HOC](https://sirwan.info/archive/2019/09/22/Higher-Order-Components/) to make the components observers of our store. So any changes to any of the observable will cause the React components to re-render.

That’s it 🚀 You’re now up and going with MobX in your React application.

[Here](https://github.com/SirwanAfifi/mobxwithts)'s the source for the project.

Originally published at https://sirwan.info/blog