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
countstate
import { makeAutoObservable } from "mobx";
import { ICounter } from "./counter";
export class CounterStore implements ICounter { count = 0;
constructor() { makeAutoObservable(this); }}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
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(); }}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.
import { makeAutoObservable } from "mobx";
import { ICounter } from "./counter";
export class CounterStore implements ICounter { count = 0;
constructor() { makeAutoObservable(this); }
increase = () => { this.count++; };
decrease = () => { this.count--; };}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.
import React from "react";
const StoreContext = React.createContext<RootStore | undefined>(undefined);- Use its provider to make store accessible to all components.
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.
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
observerHOC.
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.
{ "dependencies": { ... "mobx-state-tree": "^5.0.1", ... }}CounterStore#
mobx-state-tree provides a simpler API to create our stores and actions.
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
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.
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
mobxandmobx-reactpackages.
- npm
- yarn
npm install mobx mobx-reactyarn add mobx mobx-reactRefer to official documentation on installation for detailed usage. β