Choosing Between Mobx's makeObservable and makeAutoObservable for React State
Guide to deciding between Mobx's makeObservable and makeAutoObservable, with constraints, a comparison table, trade‑offs, sample stores, and verification steps.
09 Mar 2026, 04:19 UTC

Decision and Constraints
When building a Mobx store for a React component you must decide how to turn plain class properties into observables, actions, and computed values. The decision is driven by three constraints:
- Explicit control – you want only the intended properties to become observable.
- Boilerplate – you prefer to keep the store definition concise.
- Strict‑mode safety – you run Mobx in strict mode (
configure({ enforceActions: 'observed' })) and want to avoid warnings for unintended observables.
Comparison Table
| Feature | makeObservable | makeAutoObservable |
|---|---|---|
| Granularity | Fine‑grained – you annotate each property (observable, action, computed) | Coarse‑grained – all class fields are processed automatically |
| Boilerplate | Higher – each property needs an annotation | Lower – no annotations required |
| Strict‑mode compatibility | Works safely; only annotated items are tracked | Works but may create observables for fields you did not intend to track, triggering warnings |
| Typical use case | Libraries, complex stores where precision matters | Simple UI stores, rapid prototyping |
Trade‑offs
makeObservable gives you precision and reduces the risk of unwanted reactions, which simplifies debugging in strict mode. The cost is more verbose store definitions; teams often mitigate this by adopting a convention (e.g., always annotate actions and computed values).
makeAutoObservable speeds up initial development and reduces boilerplate, but it can cause over‑observing. New fields added later become observable automatically, which may lead to unexpected autoruns or reactions and stricter‑mode warnings that are harder to trace.
Concrete Implementation
The following example shows a todo store written twice – once with makeObservable and once with makeAutoObservable. Both versions expose the same public API: an observable todos array, an addTodo action, and a todoCount computed value.
Version A – makeObservable
import { makeObservable, observable, action, computed } from 'mobx';
class TodoStore {
todos = [];
constructor() {
makeObservable(this, {
todos: observable,
addTodo: action,
todoCount: computed
});
}
addTodo(text) {
this.todos.push({ id: Date.now(), text, completed: false });
}
get todoCount() {
return this.todos.length;
}
}
export default TodoStore;
Version B – makeAutoObservable
import { makeAutoObservable } from 'mobx';
class TodoStore {
todos = [];
constructor() {
makeAutoObservable(this);
}
addTodo(text) {
this.todos.push({ id: Date.now(), text, completed: false });
}
get todoCount() {
return this.todos.length;
}
}
export default TodoStore;
Both stores can be used with the observer wrapper from mobx-react:
import { observer } from 'mobx-react';
import TodoStore from './TodoStore';
const store = new TodoStore();
@observer
function TodoApp() {
return (
store.addTodo(e.target.value)} />
{store.todos.map(t => (
- {t.text}
))}
Total: {store.todoCount}
);
}
export default TodoApp;
Verification Steps
To confirm that only the intended properties are observable, run the following checks in a browser console or test environment:
Import Mobx utilities:
import { isObservableProp } from 'mobx'; import TodoStore from './TodoStore'; const store = new TodoStore(); console.log(isObservableProp(store, 'todos')); // true console.log(isObservableProp(store, 'addTodo')); // false (actions are not observable props) console.log(isObservableProp(store, 'todoCount')); // false (computed is not an observable prop)Enable strict mode and perform a state update outside an action:
import { configure } from 'mobx'; configure({ enforceActions: 'observed' }); // This should trigger a warning if you try to modify store.todos directly store.todos.push({ id: 1, text: 'test', completed: false }); // warning in consoleUse
autorunto see which changes cause reactions:import { autorun } from 'mobx'; autorun(() => { console.log('Todo count changed:', store.todoCount); }); // Adding a todo via the action logs; pushing directly does not (but warns in strict mode)
If the console shows warnings for unintended modifications when using makeAutoObservable, you have confirmed the trade‑off: less boilerplate but stricter‑mode noise.
Limitations and Practical Checks
The main limitation of makeObservable is the added verbosity, which can become tedious for stores with many fields. A practical mitigation is to create a helper function that encapsulates the annotation pattern for actions and computed values, reducing repetition while preserving explicit control.
For makeAutoObservable, the limitation is the risk of accidental observables. A practical way to check this is to add a temporary console.log in the constructor that logs Object.keys(this) after makeAutoObservable runs, then compare the list to the fields you deliberately marked as observable in a makeObservable version. Any extra keys indicate unintended observables.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.