What is action in MobX?
Simple definition
actionin MobX is a function that changes observable data.MobX tracks all changes made inside an action and updates dependencies (components, computations) as a single batch, without extra reactions between intermediate steps.
Why action exists
MobX separates two kinds of code:
| Kind | Purpose |
|---|---|
| Reactive | computes or renders data (for example, a React component or computed) |
| Active | changes data (for example, a click event, an API request, etc.) |
Action is the boundary where changes to observable state happen. They provide:
- predictability (all changes are controlled);
- optimization (updates are batched);
- clean reactivity (no reactions "in the middle" of computations).
Example 1 - without action
import { makeObservable, observable } from 'mobx'
class Counter {
count = 0
constructor() {
makeObservable(this, {
count: observable,
})
}
increment() {
this.count++ // MobX still works, but not optimally
}
}Here increment() changes the state, but MobX does not know that this is a controlled action.
Example 2 - with action
import { makeObservable, observable, action } from 'mobx'
class Counter {
count = 0
constructor() {
makeObservable(this, {
count: observable,
increment: action, // declare it as an action
})
}
increment() {
this.count++
}
}Now MobX knows that increment() is an action, and:
- it does not run reactions (UI updates) after every mutation;
- it delays the reaction until the end of the action;
- it groups all changes into a single batch → fewer re-renders and computations.
Automatic action detection
If you use modern MobX (via makeAutoObservable),
it marks methods as action itself, and getters as computed:
import { makeAutoObservable } from 'mobx'
class TodoStore {
todos = []
constructor() {
makeAutoObservable(this) // everything is automatic
}
addTodo(text) {
this.todos.push({ text, done: false }) // action
}
get completed() {
return this.todos.filter(t => t.done) // computed
}
}Why this matters
Suppose you update several properties in a row:
userStore.updateProfile = action(() => {
userStore.name = 'Alex'
userStore.age = 25
userStore.email = 'alex@example.com'
})Without action, MobX would trigger a reaction after every assignment, up to three re-renders in a row.
With action, MobX does this as one batch, triggering the reaction only once, after the block finishes.
Types of action
| Type | Description |
|---|---|
action(fn) | manually wraps a function in an action |
@action (decorator) | the old syntax for classes |
makeObservable(..., { method: action }) | explicit declaration |
makeAutoObservable() | automatically detects methods as actions |
Example 3 - explicit declaration outside a class
import { observable, action } from 'mobx'
const state = observable({
count: 0,
inc: action(function () {
this.count++
}),
})Under the hood
Action:
- Marks the start of a "batch of changes" (
startBatch); - Disables intermediate reactions;
- Performs the changes to the
observable; - After finishing, notifies all dependent reactions at once (
endBatch).
This is exactly what makes MobX highly efficient: the UI does not re-render on every small change, only once the whole action is done.
Summary
| What it does | Example |
|---|---|
| Wraps a state change | increment = action(() => this.count++) |
| Groups updates | One batch instead of several reactions |
| Improves performance | Fewer re-renders |
| Makes code predictable | All changes go through actions |
Automatically created by makeAutoObservable | Yes |
In short
actionis the "official place" where you changeobservabledata. It makes changes controlled, batched, and predictable, providing clean reactivity and high performance.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.