mobx-react-lite review
Mobx-react-lite 5.0.3 connects MobX 7 observables to React 18 and 19 function components. observer records observable values read during render and subscribes that component to those values; Observer creates the same tracking boundary around a render callback, and useLocalObservable creates component-owned MobX state. The package excludes class-component observation and the old Provider/inject API. Version 5 removed deprecated hooks and manual batching helpers, while 5.0.3 adds a Node export condition that routes Node and Bun to the CommonJS entry chosen for the environment. Our browser build measured 52.6 KB minified with its peer packages present, so this is an architectural choice rather than a tiny component helper.
Mobx-react-lite 5.0.3 installed in 1.3 seconds and produced a 15.8 KB gzipped full-import bundle in our sandbox, with bundled types and no audit findings. It fits teams already committed to MobX 7; a React app with only shallow local state should stay with built-in hooks or compare a selector-based store.
We installed it
| Install | ✓ · 1.3s | 3 packages on disk · 6 MB |
| Import | ✓ | ESM import works · require() works · CommonJS package with exports map |
| Browser | 15.8 KB | gzipped (52.6 KB minified), bundled with esbuild |
| Types | ✓ | TypeScript types bundled |
| Known vulns | 0 | 0 critical · 0 high · 0 moderate · 0 low (npm audit) |
Answers from our run
Does mobx-react-lite install cleanly?
Yes. In a fresh container with an empty cache, npm install mobx-react-lite finished in 1 seconds, leaving 3 packages and 6 MB on disk. npm audit reported no known vulnerabilities.
How much does mobx-react-lite add to a browser bundle?
15.8 KB gzipped (52.6 KB minified) when the whole package is bundled for the browser with esbuild. Importing only part of it is usually smaller.
Does mobx-react-lite work with both ESM and CommonJS?
Yes. Both import 'mobx-react-lite' and require('mobx-react-lite') worked in Node 22 in our run. The package is published as CommonJS with an exports map.
Does mobx-react-lite include TypeScript types?
Yes, type declarations ship inside the package, so no @types install is needed.
mobx-react-lite or zustand: which should you use?
zustand: Use it for hook-based stores with explicit selectors and less commitment to observable classes. Mobx-react-lite 5.0.3 installed in 1.3 seconds and produced a 15.8 KB gzipped full-import bundle in our sandbox, with bundled types and no audit findings.
When should you not use mobx-react-lite?
Class components must observe state. The package README supports function components only and directs class users to mobx-react.
Use it if
- The application already uses MobX 7 domain stores and needs React components to subscribe to values they read.
- Function components render deep, computed, or shared mutable models that would make handwritten selectors noisy.
- A render callback inside a third-party component needs its own Observer tracking boundary.
- The same binding must cover React 18 or 19, SSR static rendering, and React Native package conditions.
- Class components must observe state. The package README supports function components only and directs class users to mobx-react.
- The project remains on MobX 6, React 17, IE11, or a runtime without native Proxy. Version 5 requires MobX 7 and React 18 or 19.
- State is mostly local toggles, input values, and loading flags. The React guide recommends useState for simple local state and notes that local observables can limit Suspense features.
- The team expects explicit selectors and immutable snapshots at component boundaries. observer discovers reads at runtime and assumes MobX's mutable observable model.
- Bundle cost is sensitive. Our full import test produced 52.6 KB minified and 15.8 KB gzipped, which includes the code pulled through its MobX and React peers.
Setup reality
We installed mobx-react-lite 5.0.3 in a fresh unprivileged Node 22 Bookworm sandbox. npm completed in 1.3 seconds and left 3 packages using 6 MB on disk. The package is 404 KB unpacked, declares 0 direct dependencies and 2 peers, and npm audit found 0 known vulnerabilities. Both require() and ESM import worked through its exports map, and TypeScript declarations are bundled. Our browser build measured 52.6 KB minified and 15.8 KB gzipped.
Install compatible peers explicitly: MobX 7 and React 18 or 19. There are no native addons, credentials, or config files. Version 5 removed useObserver, useLocalStore, useAsObservableSource, useStaticRendering, batching imports, observerBatching, and isObserverBatched. Replace them with observer, Observer, useLocalObservable, and enableStaticRendering. Version 5.0.3's node condition directs Node and Bun to the environment-selected CommonJS build, reducing mixed import and require surprises.
Every component that reads observable data must be wrapped with observer. A wrapped parent does not subscribe an unwrapped child to values the child reads. Dereference observable properties inside the tracked component, and pass a plain projection to third-party widgets that are not observers. observer already applies React memo. Named functions inside observer produce clearer DevTools names and avoid hook-lint confusion around anonymous wrapper expressions.
Call enableStaticRendering(true) during server initialization so SSR does not retain reactions after its one render. On test runtimes without FinalizationRegistry, aborted React renders can leave cleanup timers; clearTimers in afterEach handles fake-timer and open-handle failures. useLocalObservable creates its object once, so later prop changes do not update computed dependencies unless an effect copies the prop into observable state. Keep Context provider store identity stable and mutate the store itself.
Patterns
Track observable reads during render observe-function-component
import { makeAutoObservable } from 'mobx';
import { observer } from 'mobx-react-lite';
class CounterStore {
count = 0;
constructor() { makeAutoObservable(this); }
increment() { this.count += 1; }
}
const counter = new CounterStore();
export const Counter = observer(function Counter() {
return <button onClick={() => counter.increment()}>{counter.count}</button>;
});A named function gives React DevTools a useful label and lets hook linting see the component inside observer.
Read observable props inside the observer dereference-observable-late
import { observer } from 'mobx-react-lite';
export const TodoRow = observer(function TodoRow({ todo }) {
return (
<label>
<input type="checkbox" checked={todo.done} onChange={() => todo.toggle()} />
{todo.title}
</label>
);
});Pass the observable object down. Reading todo.title in an unwrapped parent would subscribe the wrong render boundary.
Share a store with React Context provide-store-through-context
import { createContext, useContext } from 'react';
import { observer } from 'mobx-react-lite';
const StoreContext = createContext(null);
function useStore() {
const store = useContext(StoreContext);
if (!store) throw new Error('StoreContext is missing');
return store;
}
export const UserName = observer(function UserName() {
return <span>{useStore().session.userName}</span>;
});Version 5 has no MobX Provider or inject export. Keep the Context value stable while observable fields change.
Create computed local state create-local-observable
import { observer, useLocalObservable } from 'mobx-react-lite';
export const Quantity = observer(function Quantity() {
const state = useLocalObservable(() => ({
value: 1,
get doubled() { return this.value * 2; },
increment() { this.value += 1; },
}));
return <button onClick={state.increment}>{state.value} / {state.doubled}</button>;
});Use React useState for simple local flags. useLocalObservable pays off when local state is deep, computed, or passed to observer children.
Update a prop-backed computed value sync-prop-into-observable
import { useEffect } from 'react';
import { observer, useLocalObservable } from 'mobx-react-lite';
export const Total = observer(function Total({ offset }) {
const state = useLocalObservable(() => ({
total: 0,
offset,
setOffset(value) { this.offset = value; },
get adjusted() { return this.total - this.offset; },
}));
useEffect(() => state.setOffset(offset), [offset, state]);
return <output>{state.adjusted}</output>;
});React props are not observable. The initializer runs once, so an effect must copy later offset values into local observable state.
Observe a third-party render callback track-render-callback
import { Observer } from 'mobx-react-lite';
function GridCell({ todo }) {
return (
<Grid renderCell={() => (
<Observer>{() => <span>{todo.title}</span>}</Observer>
)} />
);
}The callback runs during Grid's render, outside its parent's tracked function. Observer places tracking where todo.title is read.
Project observables for a plain component pass-plain-third-party-data
import { observer } from 'mobx-react-lite';
export const Results = observer(function Results({ store }) {
const rows = store.results.map((item) => ({ id: item.id, title: item.title }));
return <ThirdPartyGrid rows={rows} />;
});A component that is not an observer will not subscribe to nested observable changes. A small projection avoids copying an entire graph with toJS.
Disable subscriptions during SSR enable-static-ssr
import { enableStaticRendering } from 'mobx-react-lite';
enableStaticRendering(typeof window === 'undefined');Run this during environment startup. Server renders should clean up after one pass instead of retaining component reactions.
Tie autorun to component lifetime dispose-autorun
import { autorun } from 'mobx';
import { useEffect } from 'react';
function useDraftSaver(draft) {
useEffect(() => autorun(() => {
localStorage.setItem('draft', draft.text);
}), [draft]);
}autorun returns a disposer. Returning it from useEffect removes the MobX reaction when the component unmounts or draft changes.
Clear fallback timers after tests clear-test-cleanup-timers
import { afterEach } from 'vitest';
import { clearTimers } from 'mobx-react-lite';
afterEach(() => {
clearTimers();
});This matters on runtimes without FinalizationRegistry, where aborted renders can leave cleanup timers visible to open-handle checks.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| zustand | npm | Use it for hook-based stores with explicit selectors and less commitment to observable classes. |
| jotai | npm | Use it when application state composes naturally from small atoms and React-first dependency tracking. |
| mobx-react | npm | Use it when the same MobX application must observe React class components or use the supported observer decorator. |
More web frontend guides
postcss · react · react-dom · tailwindcss · htmlparser2 · tailwind-merge · the whole shelf →
How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.

