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 stateMobx 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
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.
#
ActionsAn 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 componentsFirstly 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
observer
HOC.
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.
mobx-state-tree
#
Using 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 laterIf you didn't choose the plugin during project creation phase, you can follow the instructions below to add it.
- Install
mobx
andmobx-react
packages.
- npm
- yarn
npm install mobx mobx-react
yarn add mobx mobx-react
Refer to official documentation on installation for detailed usage. β