mrkeyoor.com_
Wed 23 Sept 00:33 UTC
npmWeb Frontendupdated 22 Sept 2026

mobx review

MobX 7.0.3 is a renderer-independent state library that tracks ordinary property reads and writes at runtime. You mark objects, class fields, getters, arrays, maps, or sets as observable; computed values cache derived work, actions group mutations, and reactions rerun only after a value they read changes. React rendering comes from the separate `mobx-react-lite` or `mobx-react` package. Version 7 removes the non-Proxy fallback, legacy decorators, `trace()`, and several namespaced annotations. Patch 7.0.3 adds a Node export condition so ESM imports and CommonJS requires share one MobX instance and avoid repeated `NODE_ENV` checks.

Verdict

MobX 7.0.3 installed in 0.9 seconds with 0 dependencies and 0 audit findings, but its core added 15.2 KB gzipped in our browser build; it earns that cost in applications with rich mutable domain models and many fine-grained derivations. Choose a smaller store for simple shared UI state, and use a query library when the difficult part is remote caching.

We installed it

Lab card: what happened when we installed mobxScreenshot of mobx documentation
Install✓ · 0.9s1 package on disk · 5 MB
ImportESM import works · require() works · CommonJS package with exports map
Browser15.2 KBgzipped (52.4 KB minified), bundled with esbuild
TypesTypeScript types bundled
Known vulns00 critical · 0 high · 0 moderate · 0 low (npm audit)

Answers from our run

Does mobx install cleanly?

Yes. In a fresh container with an empty cache, npm install mobx finished in 0.9s, leaving 1 package and 5 MB on disk. npm audit reported no known vulnerabilities.

How much does mobx add to a browser bundle?

15.2 KB gzipped (52.4 KB minified) when the whole package is bundled for the browser with esbuild. Importing only part of it is usually smaller.

Does mobx work with both ESM and CommonJS?

Yes. Both import 'mobx' and require('mobx') worked in Node 22 in our run. The package is published as CommonJS with an exports map.

Does mobx include TypeScript types?

Yes, type declarations ship inside the package, so no @types install is needed.

mobx or zustand: which should you use?

zustand: Use it for a compact React store with explicit selectors and a smaller conceptual surface. MobX 7.0.3 installed in 0.9 seconds with 0 dependencies and 0 audit findings, but its core added 15.2 KB gzipped in our browser build; it earns that cost in applications with rich mutable domain models and many fine-grained derivations.

When should you not use mobx?

Any supported runtime lacks native Proxy. MobX 7 removed the ES5 fallback and the useProxies configuration.

API stability4/5Observable state, computed getters, actions, `autorun()`, `reaction()`, and `observer()` have carried the same mental model across major releases. MobX 7 is still a real migration: it removes legacy decorators, non-Proxy operation, `trace()`, namespaced annotations, and old React context helpers. Patch 7.0.3 changes package routing so Node import and require share one instance without changing store code. Applications already using modern MobX 6 patterns face the smallest move.
Docs5/5mobx.js.org documents observable creation, annotation inference, computed values, actions, flows, reactions, React integration, configuration, subclass limits, and debugging with working examples. The reactivity guide spells out which synchronous reads are tracked and why destructuring or callbacks can miss updates. A dedicated MobX 7 migration table names removed annotations and React helpers with their replacements. The site also calls out disposer-related memory leaks and decorator compiler requirements.
Maintenance5/5MobX 7.0.3 shipped on August 19, 2026, and GitHub records a repository push on August 24. The release fixes Node and Bun export routing so mixed module loading uses one instance. The unarchived repository has 28,207 stars and 56 open issues and pull requests. Version 7 arrived in July with coordinated core and React-binding changes, while follow-up patches show that packaging issues are being handled quickly.
Ecosystem5/5npm counted 3,957,575 downloads from August 18 through August 24, 2026. Official React bindings cover function and class components, and the core itself does not depend on a renderer. TypeScript declarations and working ESM and CommonJS paths make stores portable across browser, Node, and tests. The surrounding ecosystem includes developer tools and utilities, though teams seeking Redux-style event logs or built-in server caching need separate libraries.

Use it if

  • The domain is naturally modeled as long-lived mutable classes with getters for derived values.
  • Fine-grained updates should follow property reads without handwritten selectors or immutable reducer code.
  • One store must run in React, Node services, tests, workers, or another view layer.
  • The team can make reaction ownership, action boundaries, and tracked read locations explicit.
Skip it if

Setup reality

We installed MobX 7.0.3 in a fresh Node 22 Bookworm sandbox in 0.9 seconds. It left one package and 5 MB on disk; the package itself was 5,064 KB unpacked. npm audit reported 0 known vulnerabilities. MobX has 0 direct and 0 peer dependencies, an MIT license, and bundled TypeScript declarations. It is published as CommonJS with an exports map, and both require() and ESM import worked in our checks.

There are no credentials, native builds, or required config files. Native Proxy support is mandatory in version 7. React users install a binding separately: mobx-react-lite covers function components, while mobx-react also covers classes. The version 7 bindings require React 18 or newer. Every component that reads observables must be wrapped in observer, and the read must happen during render. Passing a plain value extracted earlier gives MobX nothing to track.

For classes, makeAutoObservable() infers fields, getters, methods, and generators, but it cannot handle a class with super or a subclass. Use makeObservable() and current named annotations such as observableRef or actionBound there. Stage 3 decorators require TypeScript 5 or a matching Babel setup, accessor on observable fields, and no makeObservable(this) for those decorated members. Set action enforcement once near application startup rather than changing rules between modules.

Tracking covers synchronous reads only. Code after await runs outside the original action, so update through runInAction(), another action, or flow(). autorun(), reaction(), and the effect form of when() return disposers; attach each one to a service or component lifecycle. For server rendering, enable static rendering in the React binding. Our browser import measured 52.4 KB minified and 15.2 KB gzipped. Version 7.0.3's new Node condition also prevents mixed import and require paths from constructing separate MobX instances.

Patterns

Require state writes inside actions configure-actions

import { configure } from 'mobx'

configure({
  enforceActions: 'always',
  computedRequiresReaction: true,
  reactionRequiresObservable: true
})

MobX defaults `enforceActions` to `observed`. The stricter setting catches stray writes but may need deliberate exceptions in tests and lazy setup.

Infer annotations for a class store create-class-store

import { makeAutoObservable } from 'mobx'

class CartStore {
  items = []

  constructor() {
    makeAutoObservable(this, {}, { autoBind: true })
  }

  add(item) { this.items.push(item) }
  get total() {
    return this.items.reduce((sum, item) => sum + item.price, 0)
  }
}

export const cart = new CartStore()

Fields become observable, getters computed, and methods actions. Classes with a superclass or subclasses need explicit `makeObservable()` annotations.

Choose exact class annotations annotate-store

import { actionBound, computed, makeObservable, observable, observableRef } from 'mobx'

class SearchStore {
  query = ''
  results = []

  constructor() {
    makeObservable(this, {
      query: observable,
      results: observableRef,
      resultCount: computed,
      setQuery: actionBound
    })
  }

  get resultCount() { return this.results.length }
  setQuery(value) { this.query = value }
}

MobX 7 uses named exports such as `observableRef` and `actionBound`; the older namespaced forms were removed.

Declare a Stage 3 decorated model use-modern-decorators

import { action, computed, observable } from 'mobx'

class Counter {
  @observable accessor value = 0

  @computed
  get doubled() { return this.value * 2 }

  @action
  increment() { this.value += 1 }
}

Use TypeScript 5 or current Babel decorator support, add `accessor`, and remove `experimentalDecorators` plus `makeObservable(this)` for these fields.

Track a plain object deeply create-observable-object

import { observable, runInAction } from 'mobx'

const session = observable({
  user: null,
  preferences: { theme: 'system' }
})

runInAction(() => {
  session.user = { id: 'u1', name: 'Ada' }
  session.preferences.theme = 'dark'
})

`observable()` clones a plain object into a Proxy and converts nested plain values. Class instances assigned later are not converted automatically.

Resume mutation after an await update-after-await

import { runInAction } from 'mobx'

async function loadUser(store, id) {
  const response = await fetch(`/api/users/${id}`)
  if (!response.ok) throw new Error(`HTTP ${response.status}`)
  const user = await response.json()
  runInAction(() => { store.user = user })
}

An action covers one synchronous stack. Code after `await` needs a new action context when action enforcement is enabled.

Keep generator steps inside actions run-async-flow

import { flow, observable } from 'mobx'

const state = observable({ projects: [], loading: false })
const loadProjects = flow(function* () {
  state.loading = true
  try {
    const response = yield fetch('/api/projects')
    state.projects = yield response.json()
  } finally {
    state.loading = false
  }
})

const task = loadProjects()
// task.cancel()

`flow()` wraps each resumed generator step as an action and returns a cancellable promise. Its `finally` block still runs on cancellation.

Own and dispose an autorun autorun-effect

import { autorun } from 'mobx'

const dispose = autorun(() => {
  localStorage.setItem('theme', store.preferences.theme)
})

// when this service is destroyed
dispose()

The effect runs immediately and tracks synchronous observable reads. Reads inside a later timer or promise are outside the tracked pass.

React to one selected value reaction-value

import { reaction } from 'mobx'

const dispose = reaction(
  () => store.search.query.trim(),
  (query, previousQuery) => {
    analytics.track('search_changed', { query, previousQuery })
  }
)

Only the data function is tracked. The effect waits for a change by default, unlike `autorun()`, and the returned disposer must follow the owner's lifecycle.

Wait for observable state once wait-for-condition

import { when } from 'mobx'

await when(
  () => session.status === 'authenticated',
  { timeout: 10_000 }
)
startAuthenticatedWork()

The promise rejects after the 10-second timeout. The promise returned by `when()` also has a `cancel()` method.

Store records in an observable map track-dynamic-map

import { autorun, observable, runInAction } from 'mobx'

const users = observable.map()
const dispose = autorun(() => {
  console.log(users.get('u1')?.name ?? 'missing')
})
runInAction(() => users.set('u1', { name: 'Ada' }))

Maps track a missing key before it exists and avoid the descriptor-cache cost of repeatedly adding unstable property names to plain objects.

Read observables in a React render render-react-store

import { observer } from 'mobx-react-lite'

export const CartSummary = observer(function CartSummary({ cart }) {
  return (
    <button onClick={() => cart.clear()}>
      {cart.items.length} items, {cart.total.toFixed(2)}
    </button>
  )
})

Install `mobx-react-lite` separately. Read observable properties inside the observed render instead of passing values that were extracted earlier.

Alternatives

PackageRegistryPick it when
zustandnpmUse it for a compact React store with explicit selectors and a smaller conceptual surface.
@reduxjs/toolkitnpmUse it when explicit actions, immutable updates, serializable debugging, and a standard team structure matter.
xstatenpmUse it when legal states, events, guards, and workflow transitions are harder than general object reactivity.
valtionpmUse it for proxy-backed mutable state with a lighter, React-oriented API.

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.