Comparison
2n8 feels like a blend between two excellent state management libraries: Zustand and MobX. Therefore, here’s a quick comparison with those two packages.
Boilerplate
The main reason for creating 2n8 was to limit the amount of boilerplate and repetition required to make a store when using TypeScript. Here’s a simple counter example:
import { TwoAndEight, createReactStore } from '2n8'
class Store extends TwoAndEight { count = 0
addClicked() { this.count++ }
resetClicked() { this.$reset('count') }}
const useStore = createReactStore(new Store())
const Counter = () => { const count = useStore('count') const addClicked = useStore('addClicked') const resetClicked = useStore('resetClicked') return ( <div> <span>{count}</span> <button onClick={addClicked}>One up</button> <button onClick={resetClicked}>Reset</button> </div> )}
import { create } from 'zustand'
type State = { count: number}
type Actions = { addClicked: () => void resetClicked: () => void}
const initialState: State = { count: 0,}
const useStore = create<State & Actions>()((set) => ({ ...initialState, addClicked: () => set((state) => ({ ...state, count: state.count + 1, })), resetClicked: () => set((state) => ({ ...state, count: initialState.count, })),}))
const Counter = () => { const count = useStore((state) => state.count) const addClicked = useStore((state) => state.addClicked) const resetClicked = useStore((state) => state.resetClicked) return ( <div> <span>{count}</span> <button onClick={addClicked}>One up</button> <button onClick={resetClicked}>Reset</button> </div> )}
import { observer } from 'mobx-react-lite'import { makeAutoObservable } from "mobx"
type State = { count: number}
const initialState: State = { count: 0}
class Store { count = initialState.count
constructor() { makeAutoObservable(this) }
addClicked() { this.count++ }
resetClicked() { this.count = initialState.count }}
const store = new Store()
const Counter = observer(() => { return ( <div> <span>{store.count}</span> <button onClick={store.addClicked}>One up</button> <button onClick={store.resetClicked}>Reset</button> </div> )})
In this example, 2n8 requires the least store boilerplate whereas MobX needs less component binding.
The advantage of 2n8’s concise store implementation is that it doesn’t require external type definitions or an initial state object. TypeScript can infer types inside the class too; take another look at the 2n8 example, there’s no types in sight, but this store automatically has the correct types for both state and actions.
Features
2n8 | Zustand | MobX | |
---|---|---|---|
When do subscribers run? | After action (or on manual emit). | After set state. | At the end of actions. |
What equality checks are made on state changes? | Deep equality check is built-in. | Uses Object.is by default for equality, and shallow or deep equality checking must be manually added. | Deep changes are observed. |
How do components connect to state and actions? | Hooks. | Hooks. | Observer wrapper function. |
Bundle size
Bundle size | GZipped | Notes | |
---|---|---|---|
2n8 | 18.2 kB | 6.33 kB | |
Zustand | 17.4 kB | 6.53 kB | Includes useShallow hook and immer middleware to match feature parity. |
MobX | 74.9 kB | 21.9 kB |
Benchmarks
Here’s a benchmark for the libraries running in React on an Apple MacBook Air M2. It shows that the libraries all display very similar performance.
Run 1:
✓ src/react.bench.tsx > simple count 1874ms name hz min max mean p75 p99 p995 p999 rme samples · 2n8 75.8525 11.6857 14.8863 13.1835 13.6438 14.8863 14.8863 14.8863 ±1.87% 38 fastest · mobx 74.2115 11.6380 18.4382 13.4750 13.6820 18.4382 18.4382 18.4382 ±3.23% 38 · zustand 72.6212 11.7150 19.2436 13.7701 14.7164 19.2436 19.2436 19.2436 ±3.85% 37 slowest
BENCH Summary
2n8 - src/react.bench.tsx > simple count 1.02x faster than mobx 1.04x faster than zustand
Run 2:
✓ src/react.bench.tsx > simple count 1881ms name hz min max mean p75 p99 p995 p999 rme samples · 2n8 75.1111 11.7725 17.8066 13.3136 13.6982 17.8066 17.8066 17.8066 ±2.45% 38 fastest · mobx 72.5903 11.9117 17.1061 13.7759 14.4564 17.1061 17.1061 17.1061 ±3.28% 37 slowest · zustand 74.0253 11.5298 16.7901 13.5089 14.3196 16.7901 16.7901 16.7901 ±2.92% 38
BENCH Summary
2n8 - src/react.bench.tsx > simple count 1.01x faster than zustand 1.03x faster than mobx
Run 3:
✓ src/react.bench.tsx > simple count 1890ms name hz min max mean p75 p99 p995 p999 rme samples · 2n8 74.5884 11.6642 16.5621 13.4069 13.6273 16.5621 16.5621 16.5621 ±1.81% 38 · mobx 73.2655 11.6450 16.9816 13.6490 14.1171 16.9816 16.9816 16.9816 ±3.48% 37 slowest · zustand 74.8296 11.3695 16.7610 13.3637 14.3461 16.7610 16.7610 16.7610 ±3.23% 38 fastest
BENCH Summary
zustand - src/react.bench.tsx > simple count 1.00x faster than 2n8 1.02x faster than mobx