Managing Reactive State with MobX makeAutoObservable
Learn how to use MobX makeAutoObservable to eliminate state boilerplate and implement high-performance reactive data binding in JS/TS applications.
13 Jun 2026, 02:53 UTC

The Problem: Manual State Boilerplate
In complex JavaScript applications, manually defining every observable property, action, and computed value creates significant boilerplate. When state grows, developers often spend more time writing decorators or configuration objects than implementing business logic. This overhead increases the risk of missing a reactive dependency, leading to UI components that fail to update when data changes.
The takeaway: makeAutoObservable reduces this friction by automatically inferring the role of class members, allowing you to define state and logic in a standard class format while maintaining high-performance reactivity.
Prerequisites
- A project using JavaScript (ES6+) or TypeScript.
- MobX installed via
npm install mobx. - If using React,
mobx-react-liteinstalled for theobserverwrapper.
Implementing an Auto-Observable Store
The makeAutoObservable function analyzes the class prototype. It treats properties as observables, getters as computed values, and methods as actions.
Step 1: Define the Store Class
import { makeAutoObservable } from 'mobx';
class TaskStore {
// Observable: State that triggers updates
tasks = [];
filter = 'all';
constructor() {
// This makes the class reactive automatically
makeAutoObservable(this);
}
// Computed: Derived state that is cached
get filteredTasks() {
if (this.filter === 'completed') {
return this.tasks.filter(t => t.completed);
}
return this.tasks;
}
// Action: Method that modifies state
addTask(title) {
this.tasks.push({ title, completed: false });
}
setFilter(filter) {
this.filter = filter;
}
}
export const taskStore = new TaskStore();
Step 2: Bind the Store to the UI
To make a component react to the store, wrap it in the observer higher-order component. Without this, the component will not track the observables used during its render cycle.
import { observer } from 'mobx-react-lite';
import { taskStore } from './TaskStore';
const TaskList = observer(() => {
return (
taskStore.setFilter('completed')}>Show Completed
{taskStore.filteredTasks.map((task, i) => (
- {task.title}
))}
taskStore.addTask('New Task')}>Add Task
);
});
Engineering Decisions: Performance and Constraints
Computed vs. Observable
Use computed getters for any value that can be derived from existing state. MobX caches these values; the getter only re-runs if the underlying observables (like this.tasks or this.filter) change. This prevents expensive filtering or mapping operations from running on every single render.
Batching with Actions
When you modify state inside an action, MobX batches the notifications. If you update five different observables inside one action method, the observer components will only re-render once at the end of the action. Modifying state outside of actions (e.g., directly in a component) can trigger multiple unnecessary renders and makes debugging state transitions difficult.
Comparison: makeAutoObservable vs. makeObservable
| Feature | makeAutoObservable | makeObservable |
|---|---|---|
| Configuration | Automatic inference | Explicit mapping |
| Boilerplate | Minimal | Higher |
| Control | Low (follows conventions) | High (fine-grained) |
Verification and Diagnostics
To verify that the store is functioning correctly, perform these checks:
- Reactivity Check: Trigger an action (e.g.,
addTask) and verify the UI updates immediately without a manual page refresh. - Computed Cache Check: Add a
console.log('Computing...')inside thefilteredTasksgetter. Change a state property that the getter does not depend on. The log should not appear, proving the value was retrieved from cache. - Action Batching: Use the MobX DevTools to ensure that multiple state changes within a single method are grouped as one transaction.
Limitations and Risks
- Circular Dependencies: Avoid having Computed A depend on Computed B, which depends back on Computed A. This will trigger a stack overflow.
- Memory Overhead: Avoid passing massive, deeply nested arrays or objects into
makeAutoObservableif you only need to track a few properties. Over-observing large datasets can increase memory consumption. - Strict Mode: If
enforceActionsis enabled in your MobX configuration, any attempt to modify state outside an action will throw an error.
Rollback
If makeAutoObservable causes unexpected behavior due to class member naming or complex inheritance, replace it with makeObservable in the constructor to explicitly define each property:
import { makeObservable, observable, action, computed } from 'mobx';
constructor() {
makeObservable(this, {
tasks: observable,
filter: observable,
filteredTasks: computed,
addTask: action,
setFilter: action,
});
}0 replies
A thoughtful contribution can make all the difference. Be the first to share one.