Skip to content
Back to skills

Mobx

ASecurity

MobX is a state management library for JavaScript and React that makes state observable, so computed values and components update automatically when it changes. Use when a user asks to add MobX to a React app, create a store with makeAutoObservable, wrap components in observer, write computed values, actions, reactions or flow, fix a MobX component that does not re-render, or migrate from MobX 6 to MobX 7.

  • 142 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added September 6, 2026
developmentjavascripttypescriptgojavabashreactexpressgitapi

Works with

  • terminal
  • cli
  • api

Security analysis

A96/100
  • mediumInstalls packages at runtime which could introduce malicious dependencies

Pro scans all 2 files and shows the line behind each finding

Scanned October 4, 2026

npx -y skills add TerminalSkills/skills --skill mobx --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Mobx?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Mobx
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/terminalskills-mobx/badge)](https://www.skillsdirectory.com/skills/terminalskills-mobx)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: mobx
description: >-
  MobX is a state management library for JavaScript and React that makes state
  observable, so computed values and components update automatically when it
  changes. Use when a user asks to add MobX to a React app, create a store with
  makeAutoObservable, wrap components in observer, write computed values,
  actions, reactions or flow, fix a MobX component that does not re-render, or
  migrate from MobX 6 to MobX 7.
license: Apache-2.0
compatibility: 'MobX 7 with mobx-react-lite 5 (React 18 or 19); needs native Proxy support'
metadata:
  author: terminal-skills
  version: 1.1.0
  category: development
  repository: https://github.com/mobxjs/mobx
  tags:
    - react
    - state-management
    - observable
    - reactive
    - proxy
---

# MobX — Reactive State Management

## Overview

MobX is a simple and scalable state management library based on transparent reactive programming. State is made observable, derived values are computed from it, actions mutate it, and reactions run side effects — the UI updates automatically when the state it read changes, without manual subscriptions. Stores can be classes or plain objects.

Current major version: **MobX 7** (July 2026) with `mobx-react-lite` 5 for function components and `mobx-react` 10 for class components. MobX 7 is a cleanup release: Proxy-only, Stage 3 decorators only, and several namespaced APIs became named exports (see "MobX 7 changes" below).

## Instructions

### Installation

```bash
npm install mobx mobx-react-lite
```

Use `mobx-react` instead of `mobx-react-lite` only when class components or the `@observer` class decorator are needed. With TypeScript, set `"useDefineForClassFields": true` in `tsconfig.json`; without spec-compliant class fields, a field declared without an initial value (`user?: User;`) cannot be made observable.

### Observable Store

```typescript
// stores/todo-store.ts
import { makeAutoObservable, runInAction } from "mobx";

export interface Todo { id: string; text: string; done: boolean }

export class TodoStore {
  todos: Todo[] = [];
  filter: "all" | "active" | "done" = "all";
  isLoading = false;

  constructor() {
    // Infers observables, computeds (getters), actions (methods), flows (generators).
    // autoBind lets methods be passed around as callbacks: onClick={store.addTodo}
    makeAutoObservable(this, {}, { autoBind: true });
  }

  // Computed (cached, recalculated only when dependencies change)
  get filteredTodos() {
    switch (this.filter) {
      case "active": return this.todos.filter(t => !t.done);
      case "done": return this.todos.filter(t => t.done);
      default: return this.todos;
    }
  }

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

  // Actions (state mutations)
  addTodo(text: string) {
    this.todos.push({ id: crypto.randomUUID(), text, done: false });
  }

  toggleTodo(id: string) {
    const todo = this.todos.find(t => t.id === id);
    if (todo) todo.done = !todo.done;     // Direct mutation — MobX tracks it
  }

  removeTodo(id: string) {
    this.todos = this.todos.filter(t => t.id !== id);
  }

  // Async action: code after an await is no longer inside the action
  async fetchTodos() {
    this.isLoading = true;
    try {
      const response = await fetch("/api/todos");
      const data: Todo[] = await response.json();
      runInAction(() => { this.todos = data; });
    } finally {
      runInAction(() => { this.isLoading = false; });
    }
  }

  // Same thing as a flow: a generator needs no runInAction after each yield
  *syncTodos(): Generator<Promise<unknown>, number, any> {
    this.isLoading = true;
    try {
      const response = yield fetch("/api/todos");
      this.todos = yield response.json();
      return this.todos.length;
    } finally {
      this.isLoading = false;
    }
  }
}
```

Call a flow like a normal method; in TypeScript wrap it to get a typed promise: `const count = await flowResult(store.syncTodos())`.

### Observer Components

```tsx
import { createContext, useContext } from "react";
import { observer, useLocalObservable } from "mobx-react-lite";
import { TodoStore } from "./stores/todo-store";

const StoreContext = createContext<TodoStore | null>(null);
const useStore = () => {
  const store = useContext(StoreContext);
  if (!store) throw new Error("StoreContext.Provider is missing");
  return store;
};

// observer tracks which observables the render read and re-renders only for those
const TodoList = observer(() => {
  const store = useStore();
  if (store.isLoading) return <p>Loading…</p>;
  return (
    <div>
      <p>{store.remaining} remaining</p>
      <ul>
        {store.filteredTodos.map(t => (
          <li key={t.id} onClick={() => store.toggleTodo(t.id)}
            style={{ textDecoration: t.done ? "line-through" : "none" }}>
            {t.text}
          </li>
        ))}
      </ul>
    </div>
  );
});

// Component-local observable state
const Counter = observer(() => {
  const state = useLocalObservable(() => ({ count: 0, increment() { this.count++; } }));
  return <button onClick={state.increment}>Clicked {state.count} times</button>;
});

export const App = ({ store }: { store: TodoStore }) => (
  <StoreContext.Provider value={store}><TodoList /><Counter /></StoreContext.Provider>
);
```

### Reactions

```typescript
import { autorun, reaction, when } from "mobx";

// reaction: runs the effect when the tracked expression changes (not on creation)
const dispose = reaction(
  () => store.remaining,
  (remaining, previous) => { document.title = `${remaining} todos left (was ${previous})`; },
);

// autorun: runs once immediately, then whenever anything it read changes
const stopSaving = autorun(() => {
  localStorage.setItem("todos", JSON.stringify(store.todos));
});

await when(() => !store.isLoading);   // promise that resolves once the condition is true

dispose(); stopSaving();              // always dispose reactions you no longer need
```

### MobX 7 changes (upgrading from MobX 6)

| MobX 6 | MobX 7 |
|--------|--------|
| `observable.ref` / `.shallow` / `.deep` / `.struct` | `observableRef`, `observableShallow`, `observableDeep`, `observableStruct` |
| `computed.struct`, `action.bound`, `flow.bound` | `computedStruct`, `actionBound`, `flowBound` |
| `comparer.structural` / `.shallow` / `.default` / `.identity` | `compareStructural`, `compareShallow`, `compareDefault`, `compareIdentity` |
| `configure({ useProxies })`, `{ proxy: false }` | removed — Proxy is always used |
| `trace()` | removed — use `getDependencyTree`, `getObserverTree`, `spy` |
| Legacy decorators (`experimentalDecorators`) | Stage 3 decorators only: `@observable accessor title = ""`, no `makeObservable(this)` |
| `Provider`, `inject` (mobx-react) | `React.createContext` |
| `useLocalStore`, `useObserver`, `useStaticRendering` | `useLocalObservable`, `observer` / `<Observer>`, `enableStaticRendering` |

`mobx-react-lite` 5 and `mobx-react` 10 require MobX 7 and React 18+. Projects that must keep legacy decorators stay on MobX 6.

## Examples

### Example 1: Cart store with decorators

**User request:** "Add a MobX cart store to our checkout — the total should update when items or the coupon change."

Stage 3 decorators need TypeScript 5+ with `experimentalDecorators` off (or Babel's `@babel/plugin-proposal-decorators` with `version: "2023-05"`).

```typescript
import { observable, computed, action, actionBound, autorun } from "mobx";

class CartStore {
  @observable accessor items: { sku: string; price: number; qty: number }[] = [];
  @observable accessor coupon: string | null = null;

  @computed get total() {
    const sum = this.items.reduce((s, i) => s + i.price * i.qty, 0);
    return this.coupon === "WELCOME10" ? sum * 0.9 : sum;
  }

  @action add(sku: string, price: number, qty = 1) {
    this.items.push({ sku, price, qty });
  }

  @actionBound applyCoupon(code: string) { this.coupon = code; }
}

const cart = new CartStore();
const stop = autorun(() => console.log("total", cart.total));
cart.add("TSHIRT-M", 24, 2);
cart.applyCoupon("WELCOME10");
stop();
```

Output — the autorun fires once at start and once per change:

```
total 0
total 48
total 43.2
```

### Example 2: Upgrade a store from MobX 6 to MobX 7

**User request:** "We bumped mobx to 7 and the session store throws on `observable.ref`. Fix it."

```bash
npm install mobx@7 mobx-react-lite@5
```

```typescript
// Before (MobX 6): makeObservable(this, { user: observable.ref, signIn: action.bound })
import { makeObservable, observableRef, actionBound, autorun } from "mobx";

class SessionStore {
  user: { id: string; name: string } | null = null;

  constructor() {
    makeObservable(this, { user: observableRef, signIn: actionBound });
  }

  signIn(user: { id: string; name: string }) { this.user = user; }
}

const session = new SessionStore();
autorun(() => console.log("user", session.user?.name));
session.signIn({ id: "u_81", name: "Priya Nair" });
```

Output: `user undefined`, then `user Priya Nair`. Also remove `useProxies` from any `configure()` call and replace `Provider`/`inject` with a React context as shown above.

## Guidelines

1. **makeAutoObservable** — Use in the constructor; it makes properties observable, getters computed, methods actions and generators flows. It cannot be used in classes that extend another class or are subclassed — use `makeObservable` with explicit annotations there
2. **observer()** — Wrap every React component that reads observables with `observer`; it only re-renders when the observables it read change
3. **Direct mutations** — Mutate state directly in actions (`this.todos.push(...)`) — MobX uses Proxy to track changes
4. **runInAction or flow** — State changes after an `await` must be wrapped in `runInAction()`, or write the method as a generator (`flow`). Changing observed state outside an action logs a strict-mode warning (`enforceActions: "observed"` is the default)
5. **Computed values** — Use getters for derived data; MobX caches results and recalculates only when dependencies change. Keep them free of side effects
6. **Reactions for side effects** — Use `reaction()` or `autorun()` for logging, localStorage sync, API calls on state change, and call the returned disposer when done; inside components create them in `useEffect` and return the disposer
7. **Small stores** — Create multiple domain stores (AuthStore, CartStore, UIStore); share them through React context or a module import
8. **Don't destructure too early** — `const { count } = store` outside an `observer` component or reaction copies the value and loses tracking; read `store.count` where it is used
9. **Serializing** — Use `toJS(store.todos)` to get a plain, non-observable copy. Fields declared with `@observable accessor` are not enumerable, so `JSON.stringify(store)` and `Object.keys(store)` skip them unless the class defines `toJSON`
10. **When not to use it** — MobX 7 needs native `Proxy`, so it cannot run on engines without it; for a couple of local values plain React state is enough

Files in this skill

  • SKILL.md4.5 KB
  • _scores.json1.8 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…