Diagnosing MobX Reactivity Failures: A Step‑by‑Step Checklist
When MobX observables stop updating components, follow this diagnostic guide to pinpoint the issue – from missing decorators to strict‑mode mutations – and restore proper reactivity.
06 Jan 2026, 15:39 UTC

Problem Statement
In a React application that uses MobX 6.x, you may notice that component re‑renders, computed values, or reactions do not fire when an observable changes. The UI appears frozen, logs show no change notifications, or devtools show no update tree. This guide walks you through the most common reasons for this failure and how to fix them.
Typical Symptoms
- Counter value displayed in a component does not increment after clicking a button.
- Computed list remains unchanged even though underlying observables have been updated.
- MobX devtools show the observable node but no reaction or observer nodes attached.
- Console displays warnings such as "Strict mode: attempting to modify state outside of an action".
Common Causes – Quick Reference Table
| Cause | Why It Happens | Typical Fix |
|---|---|---|
| Observable not declared | Missing @observable or observable() | Add the decorator or call observable() |
| Computed not marked | Missing @computed or computed() | Mark the getter with @computed or wrap with computed() |
| Strict mode mutation | State mutated outside @action | Wrap mutation in @action or disable strict mode |
| Component not observed | Missing observer wrapper or wrong import | Wrap component with observer() from mobx-react-lite |
| Legacy decorators without makeObservable | Decorators ignored in constructor | Call makeObservable(this) before accessing fields |
| Stale closure reference | Component captures old observable value | Re‑create component or use useEffect to reset reference |
| Devtools disconnected | Changes not visible in UI | Enable MobX devtools extension and connect |
Diagnostic Checklist
- Verify Observable Declaration
import { makeAutoObservable } from "mobx"; class CounterStore { count = 0; constructor() { makeAutoObservable(this); // ensures all fields are observable } } export const counter = new CounterStore();Run
npm ls mobxto confirm the correct version. If you use decorators, ensureexperimentalDecoratorsandemitDecoratorMetadataare true intsconfig.json. - Check Computed Getters
@computed get double() { return this.count * 2; }Open devtools; the computed node should appear under the observable. If missing, the decorator was not applied.
- Inspect Strict Mode Warnings
// In index.tsx import { enableStaticRendering } from "mobx-react-lite"; enableStaticRendering(false); // default is falseRun the app and look for console errors about mutations outside actions. If present, wrap the mutation:
@action.bound increment() { this.count++; } - Confirm Observer Wrapper
import { observer } from "mobx-react-lite"; const CounterDisplay = observer(() => { return {counter.count}; });Without
observer, the component will not re‑render on observable changes. - Test with Devtools
Open the MobX devtools extension, click Refresh, then perform the action that should trigger a change. Verify that the observable node shows a new value and that the observer node is highlighted.
- Check for Stale Closures
If you pass the store to a child component via props, ensure the child does not capture a stale reference:
const Child = observer(({ store }) => { return {store.count}; }); - Unit‑Test Verification (Optional)
Use
mobx-utilsto spy on a reaction:import { reaction } from "mobx"; let called = false; reaction(() => counter.count, () => { called = true; }); counter.increment(); console.assert(called, "Reaction did not fire");
Fixes Tied to Findings
- Missing
@observable– add the decorator or switch tomakeAutoObservable. - Missing
@computed– annotate the getter or wrap withcomputed(() => ...). - Strict mode violation – wrap all state changes in
@actionor disable strict mode withconfigure({ enforceActions: "never" }). - Component not observed – import
observerfrommobx-react-liteand wrap the component. - Legacy decorators – call
makeObservable(this)in the constructor before any field access. - Stale closure – recreate the component or use
useEffectto reset references. - Devtools disconnected – install the browser extension, ensure
mobx-devtools-mstis added todevtoolsconfig, and refresh the page.
Escalation Criteria
- If all diagnostics above return clean but the UI still does not update, suspect a deeper integration issue (e.g., mixing
mobx-state-treewith plain MobX). - Check for concurrent React features like
ConcurrentModethat may affect rendering; ensureobserveris used correctly withuseObserverif needed. - Consult the official MobX issue tracker or community forums with the minimal reproducible example; include the diagnostic steps you performed.
By following this structured approach, you can isolate whether the problem lies in declaration, mutation discipline, component wiring, or tooling, and apply the appropriate fix without unnecessary trial and error.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.