Skip to main content

Mobx

Simple, scalable state management.

Anything that can be derived from the application state, should be. Automatically.

MobX is a battle tested library that makes state management simple and scalable by transparently applying functional reactive programming.
Refer to official documentation for detailed usage. β†’

Observable state#

Mobx uses observables for store values. Properties, entire objects, arrays, Maps and Sets can all be made observable.
Refer to official documentation on observable state for detailed usage. β†’

Making observable stores with classes#

  • Make a counter store that holds a count state
src/mobx/stores/counter/index.ts
import { makeAutoObservable } from "mobx";
import { ICounter } from "./counter";
export class CounterStore implements ICounter {  count = 0;
  constructor() {    makeAutoObservable(this);  }}
src/mobx/stores/counter/counter.d.ts
export interface ICounter {  count: number;}
info

makeAutoObservable and its cousin makeObservable trap existing object properties and make them observable. makeAutoObservable is like makeObservable on steroids, as it infers all the properties by default.

  • Make a root store that holds the counter store
src/mobx/stores/index.ts
import { iroot } from "./store";import { CounterStore } from "./counter";import { ICounter } from "./counter/counter";
export class RootStore implements IRoot {    counterStore: ICounter;
    constructor() {        this.counterStore = new CounterStore();    }}
src/mobx/stores/store.d.ts
import { ICounter } from "./stores/counter/counter";
export interface iroot {  counterStore: ICounter;}

Before starting to read data from the store, let's add some action.

Actions#

An action is any piece of code that modifies the state.

  • Add actions to counter store.
src/mobx/stores/counter/index.ts
import { makeAutoObservable } from "mobx";
import { ICounter } from "./counter";
export class CounterStore implements ICounter {  count = 0;
  constructor() {    makeAutoObservable(this);  }
  increase = () => {    this.count++;  };
  decrease = () => {    this.count--;  };}
src/mobx/stores/counter/counter.d.ts
export interface ICounter {  count: number;  increase: () => void;  decrease: () => void;}

Using store in components#

Firstly store must be made accessible to components. It can be done with React.useContext. Then with a custom hook, store can be read from components.

  • Make a context to hold store.
src/mobx/index.tsx
import React from "react";
const StoreContext = React.createContext<RootStore | undefined>(undefined);
  • Use its provider to make store accessible to all components.
src/mobx/index.tsx
import React from "react";import { IRoot } from "./stores/store";
let store: IRoot;
const StoreContext = React.createContext<RootStore | undefined>(undefined);
export const RootStoreProvider = ({  children,}: {  children: React.ReactNode;}) => {  const root = store ?? new RootStore();
  return <StoreContext.Provider value={root}>{children}</StoreContext.Provider>;};
  • Components can read from store via a custom hook.
src/mobx/index.tsx
import React from "react";import { IRoot } from "./stores/store";
let store: IRoot;
const StoreContext = React.createContext<RootStore | undefined>(undefined);
export const RootStoreProvider = ({  children,}: {  children: React.ReactNode;}) => {  const root = store ?? new RootStore();
  return <StoreContext.Provider value={root}>{children}</StoreContext.Provider>;};
export const useRootStore = () => {  const context = React.useContext(StoreContext);  if (context === undefined) {    throw new Error("useRootStore must be used within RootStoreProvider");  }
  return context;};
  • Wrap your component with observer HOC.
Your Component
import { observer } from "mobx-react";import { useRootStore } from "@mobx";
export const MobxExample: React.FC = observer(() => {  const { counterStore } = useRootStore();  const { count, increase, decrease } = counterStore;
  return (    <div>      <div>        <h2>Counter</h2>        <button          type="button"          onClick={increase}        >          +        </button>        <span>{count}</span>        <button          type="button"          onClick={decrease}        >          -        </button>      </div>    </div>  );});

Refer to official documentation on React integration for detailed usage. β†’

tip

You might consider using mobx-react-lite instead of mobx-react if you're not using class components.

Using mobx-state-tree#

Mobx State Tree provides a better structured state management and tools you need in your app. If you want to use mobx-state-tree, you need to make some changes to the files you've created above.

package.json
{  "dependencies": {    ...      "mobx-state-tree": "^5.0.1",    ...  }}

CounterStore#

mobx-state-tree provides a simpler API to create our stores and actions.

mobx/stores/counter/index.ts
import { types } from "mobx-state-tree";
export const CounterStore = types    .model("Counter", {        count: 0,    })    .actions((counter) => ({        increase() {            counter.count += 1;        },        decrease() {            counter.count -= 1;        },    }));

RootStore#

After the changes in CounterStore we will need to update RootStore with mobx-state-tree

mobx/stores/index.ts
import { types } from "mobx-state-tree";import { CounterStore } from "./counter";
export const RootStore = types.model("RootStore", {    counterStore: CounterStore,});
export const createRootStore = () =>    RootStore.create({        counterStore: CounterStore.create(),    });

RootStoreProvider#

Finally, you need to update RootStoreProvider with the createRootStore function.

mobx/index.tsx
import React from "react";
import { createRootStore } from "./stores";
const StoreContext = React.createContext<    ReturnType<typeof createRootStore> | undefined>(undefined);
export const RootStoreProvider = ({    children,}: {    children: React.ReactNode;}) => {    const root = createRootStore();
    return (        <StoreContext.Provider value={root}>{children}</StoreContext.Provider>    );};
export const useRootStore = () => {    const context = React.useContext(StoreContext);    if (context === undefined) {        throw new Error("useRootStore must be used within RootStoreProvider");    }
    return context;};

Adding mobx to your project later#

If you didn't choose the plugin during project creation phase, you can follow the instructions below to add it.

  • Install mobx and mobx-react packages.
npm install mobx mobx-react

Refer to official documentation on installation for detailed usage. β†’