Reactive State

MobX

MobX is reactive state management: annotate your data, and everything that depends on it updates automatically. No selectors, no reducers, just observables.

intermediate13 min readUpdated Sep 15, 2026
store.js
js
// store.js
import { makeAutoObservable } from "mobx";

class CounterStore {
  count = 0;

  constructor() {
    makeAutoObservable(this);
  }

  increment() {
    this.count += 1;
  }

  get double() {
    return this.count * 2;
  }
}

export const counter = new CounterStore();
Model
Transparent reactivity
State
Observables
Derived
Computed values
Updates
Actions
React
observer HOC
Boilerplate
Very low

Why it matters

Why MobX feels automatic

Automatic tracking

You do not wire up subscriptions. MobX records what each component reads and updates it when that data changes.

Derived values stay fresh

Computed values recalculate only when their dependencies change and are cached in between.

Fine-grained updates

Only the components that actually read a changed observable re-render, which keeps large apps responsive.

The big picture

The three ideas behind MobX

Observable state, computed values derived from it, and actions that change it. Reactions connect all of that to the UI.

Observables

State

Plain data marked as observable so MobX can track every read and write.

Computed

Derive

Values calculated from observables, cached and invalidated automatically.

Actions and observer

Update & render

Actions change state, and observer components subscribe to whatever they read.

MobX at a glance

The core of MobX

makeObservable

Annotate fields, getters and methods to define observables, computed values and actions.

makeAutoObservable

Infer the annotations automatically for a class, which removes most boilerplate.

computed

Cache a derived value and recompute it only when dependencies change.

action

Mark the functions that change state so updates are batched and traceable.

observer

Wrap a React component so it re-renders when the observables it reads change.

reactions

Run side effects in response to state changes, outside the render tree.

A short history

Reactive state through the years

  1. 2015

    MobX released

    Michel Weststrate introduces transparent reactive state to the JavaScript world.

    15
  2. 2016

    MobX 2 and 3

    Better observability and performance make it popular with React developers.

    16
  3. 2018

    MobX 4 and 5

    Proxies replace property descriptors, and class decorators become optional.

    18
  4. 2020

    MobX 6

    makeAutoObservable and the removal of decorators simplify the modern API.

    20
  5. Today

    A mature reactive option

    A stable choice for teams that prefer automatic dependency tracking over explicit selectors.

    Today

The complete guide

MobX: Everything you need to know

What is MobX?

MobX is a reactive state management library. You mark your data as observable, and MobX tracks everything that reads it. When the data changes, the values and components that depend on it update automatically. There are no selectors to write and no subscriptions to maintain.

The philosophy is almost the opposite of Redux. Where Redux makes every change explicit and traceable through actions and reducers, MobX makes changes implicit through automatic dependency tracking. You write normal object-oriented code, and the reactivity happens underneath.

Observables and makeAutoObservable

The simplest way to build a store is a class annotated with makeAutoObservable, which infers what should be observable, computed and an action.

// store.js
import { makeAutoObservable } from "mobx";

class TodoStore {
  todos = [];

  constructor() {
    makeAutoObservable(this);
  }

  add(text) {
    this.todos.push({ id: Date.now(), text, done: false });
  }

  toggle(id) {
    const todo = this.todos.find((t) => t.id === id);
    if (todo) todo.done = !todo.done;
  }

  get remaining() {
    return this.todos.filter((t) => !t.done).length;
  }
}

export const todoStore = new TodoStore();

makeAutoObservable treats fields as observables, getters as computed values and methods as actions. You write plain JavaScript — this.todos.push(...) — and MobX tracks it.

Computed values

A computed value derives data from observables. It is cached, and it recalculates only when one of its dependencies changes.

// cart.js
class Cart {
  items = [];

  constructor() {
    makeAutoObservable(this);
  }

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

  get isEmpty() {
    return this.items.length === 0;
  }
}

Because total is computed, reading it in a hundred places costs one calculation. It also stays consistent: change an item’s price and every reader sees the new total without any manual invalidation.

Actions

Actions are the functions that change state. Marking them explicitly lets MobX batch updates and keeps changes traceable.

// actions.js
import { action, makeObservable, observable } from "mobx";

class Settings {
  theme = "dark";
  fontSize = 16;

  constructor() {
    makeObservable(this, {
      theme: observable,
      fontSize: observable,
      update: action,
    });
  }

  update({ theme, fontSize }) {
    if (theme) this.theme = theme;
    if (fontSize) this.fontSize = fontSize;
  }
}

With makeAutoObservable you get this automatically. The benefit of actions is batching: several changes inside one action produce a single reaction, not one per assignment. MobX can also enforce that state only changes inside actions, which catches accidental mutations.

Connecting to React with observer

The observer higher-order component subscribes a React component to the observables it reads during render.

// TodoList.jsx
import { observer } from "mobx-react-lite";
import { todoStore } from "./store";

export const TodoList = observer(() => {
  return (
    <div>
      <p>{todoStore.remaining} remaining</p>
      <ul>
        {todoStore.todos.map((todo) => (
          <li key={todo.id} onClick={() => todoStore.toggle(todo.id)}>
            {todo.done ? "✓" : "○"} {todo.text}
          </li>
        ))}
      </ul>
      <button onClick={() => todoStore.add("New task")}>Add</button>
    </div>
  );
});

The component never lists its dependencies. MobX records them as the component renders, and re-renders it when any of them change. Only components that actually read a changed observable update, which is fine-grained by construction.

Reactions and async state

A reaction runs a side effect when observed data changes, outside the render tree. Use them for logging, persistence or syncing with external systems.

// persist.js
import { autorun } from "mobx";

autorun(() => {
  localStorage.setItem("theme", settings.theme);
});

Async actions follow the same pattern as any class method. MobX even ships flow for generator-based async flows, but a plain async method with runInAction for the final state update works well.

// users.js
import { makeAutoObservable, runInAction } from "mobx";

class UserStore {
  users = [];
  loading = false;

  constructor() {
    makeAutoObservable(this);
  }

  async fetchUsers() {
    this.loading = true;
    const res = await fetch("/api/users");
    const users = await res.json();
    runInAction(() => {
      this.users = users;
      this.loading = false;
    });
  }
}

MobX compared

MobX, Redux and Zustand all manage state, but the mental models differ.

  • MobX tracks dependencies automatically. You write mutable-looking code and get fine-grained updates for free.
  • Redux makes changes explicit through actions and reducers, which gives strict structure and powerful devtools.
  • Zustand sits in between: a tiny store with explicit selectors and almost no ceremony.

If you like object-oriented stores and dislike writing selectors, MobX is a pleasure. If you prefer explicit, functional updates and a single immutable tree, Redux is a better fit. If you want something small and predictable, look at Zustand.

Best practices

  • Use makeAutoObservable for stores and observer for components.
  • Keep derived data in computed getters instead of duplicating calculations.
  • Mark state changes as actions so updates batch and stay traceable.
  • Keep stores as plain classes and avoid storing non-observable UI state in them.
  • Use runInAction for state updates after an await.
  • Keep server data in a data-fetching library rather than a MobX store.
  • Do not read observables outside a reaction or an observer and expect updates.

Common mistakes

  • Forgetting observer and wondering why the component does not update.
  • Reading an observable once and caching the value, losing reactivity.
  • Storing large non-observable objects and missing changes.
  • Mutating state outside an action when strict mode is enabled.
  • Overusing reactions for things that should be computed.
  • Treating MobX as a server-state cache.

Where to go next

MobX is a mature, automatic approach to reactive state. Compare it with the explicit model of Redux Toolkit and the minimalism of Zustand, and move server data to TanStack Query. Then build a small store and watch how little wiring it takes to keep the UI in sync.

Component state

With observer, a component re-renders when the observables it reads change, without selectors or hooks.

MobX
import { observer } from "mobx-react-lite";
import { counter } from "./store";

export const Display = observer(() => (
  <p>{counter.count}</p>
));
useState
const [count, setCount] = useState(0);
// plus effects and props
// to keep it in sync

Derived values

computed caches the result and recalculates only when a dependency changes.

Prefer
class Cart {
  items = [];
  constructor() {
    makeAutoObservable(this);
  }
  get total() {
    return this.items.reduce(
      (sum, i) => sum + i.price, 0,
    );
  }
}
Avoid
// recomputed on every access
// and duplicated in each
// component that needs it

FAQ

Frequently asked questions

Keep learning

Related topics from the roadmap.

$ start learning

Ready to start learning MobX?

Our interactive tutorial walks you through MobX step by step — with quizzes and real code you can run in the browser.