React Integration, Async Actions & When to Reach for MobX
observer — Fine-Grained Re-Rendering
const TodoList = observer(() => (
<ul>
{store.todos.map((t) => (
<li key={t.text}>{t.text}</li>
))}
</ul>
))observer subscribes a component to exactly the observables it reads during render, giving fine-grained re-rendering — only components that actually read a piece of changed state re-render, without manual selector-style subscriptions.
A Common Gotcha: Reading Outside Render
MobX tracks dependencies by recording what's read during the tracked render. Destructuring an observable value too early — before the observer-wrapped render actually accesses it — breaks fine-grained tracking for that value, a classic source of "why didn't this re-render" bugs.
Async Actions
async fetchTodos() {
const data = await api.getTodos()
runInAction(() => {
this.todos = data
})
}Actions are meant to be synchronous, so code after an await isn't automatically covered by the outer action — runInAction wraps the resulting state mutation inline, batching it into a single reactive update.
Strict Mode & MobX State Tree
configure({ enforceActions: 'observed' }) throws if a mutation happens outside a tracked action, catching accidental stray mutations. For larger apps wanting more structure, MobX State Tree (MST) layers a typed, serializable tree model with schema validation and snapshot/time-travel support on top of MobX's reactivity.
When MobX Is a Good Fit
MobX's low-ceremony, mutation-friendly model tends to shine for rich, object-oriented domain models with lots of interdependent derived state — complex forms, drawing/editor tools — where Redux's explicit reducer boilerplate can feel heavy. Redux's explicit action→reducer flow remains easier to trace/log/replay for teams that value that discipline.
Keep your own version of these notes — editable, searchable, and organised by your stack.
Start free