Stop Syncing State: Using MobX Computed Values for Derived Data
Stop manually syncing state in React. Learn how MobX computed values automate derived data, improve performance through memoization, and eliminate synchronization bugs.
21 Feb 2026, 22:50 UTC

The Problem: The State Synchronization Trap
A common pattern in React applications is creating a piece of state that depends on another piece of state. For example, you might have a list of products and a separate filteredProducts array. Whenever the search query or the product list changes, you manually update the filtered list.
This approach creates a synchronization burden. If you update the source data but forget to trigger the update for the derived data, your UI becomes inconsistent. This "manual sync" leads to fragmented logic across components and increases the surface area for bugs.
The Solution: Declarative Derived State
MobX solves this with computed values. Instead of storing a filtered list as a separate observable that you must manually update, you define a computed property that describes how to derive the value from the source.
Computed values are highly efficient because they are lazy and memoized. They only re-calculate if the underlying observables they depend on have changed, and only if some observer (like a React component) is actually using that value.
Core Characteristics of Computed Values
- Automatic Tracking: MobX tracks which observables are accessed during the computation and automatically marks the computed value as stale when those observables change.
- Purity: In strict mode, computed values are read-only. They cannot modify other observables, ensuring a one-way data flow.
- Fine-Grained Reactivity: Components wrapped in
observerwill only re-render if the final result of the computed value actually changes, preventing unnecessary DOM updates.
Worked Example: A Dynamic Product Filter
In this example, we assume MobX v6+ and mobx-react-lite. We define a store where the filtered list is derived automatically from the search term and the raw data.
import { makeAutoObservable } from 'mobx';
import { observer } from 'mobx-react-lite';
class ProductStore {
searchQuery = '';
products = [
{ id: 1, name: 'Mechanical Keyboard' },
{ id: 2, name: 'Gaming Mouse' },
{ id: 3, name: 'UltraWide Monitor' },
];
constructor() {
// makeAutoObservable automatically infers getters as computed values
makeAutoObservable(this);
}
// This is a computed value
get filteredProducts() {
console.log('Computing filtered list...');
return this.products.filter(p =>
p.name.toLowerCase().includes(this.searchQuery.toLowerCase())
);
}
setSearchQuery(query) {
this.searchQuery = query;
}
}
const store = new ProductStore();
// React Component
const ProductList = observer(({ store }) => {
return (
store.setSearchQuery(e.target.value)}
placeholder="Search..."
/>
{store.filteredProducts.map(p => - {p.name}
)}
);
});
Verification and Risks
To verify this implementation, check the browser console. You will notice that "Computing filtered list..." only logs when searchQuery changes. If the component re-renders for a reason unrelated to the store, the computed value is returned from cache without re-running the filter logic.
Risk: If you replace the products array entirely (e.g., this.products = newData) instead of mutating it or using MobX-aware methods, ensure the property is marked as observable so the dependency tracker can see the replacement.
Trade-offs and Limitations
While computed values are powerful, they are not free. The primary trade-off is the memory overhead of the dependency graph MobX maintains to track these relationships.
Performance Bottlenecks
If a computed value performs an extremely expensive operation (like sorting a 10,000-item array), and it is accessed by dozens of components, the initial calculation can cause a frame drop. To mitigate this:
- Chain Computeds: Break complex logic into smaller computed values. If Computed B depends on Computed A, B only re-runs if A's result actually changes.
- Avoid Side Effects: Never trigger an API call or mutate state inside a getter. This will throw an error in strict mode and create unpredictable render loops in non-strict mode.
Practical Implementation Checklist
- Identify Derived State: Look for
useEffectblocks that simply update one state variable based on another. These are candidates forcomputed. - Implement as Getters: Use JS getters within a
makeAutoObservableclass to keep the syntax clean. - Verify with DevTools: Use the MobX DevTools to inspect the dependency tree and ensure you aren't creating circular dependencies.
- Enforce Purity: Enable strict mode to ensure your computed values remain pure functions of their observables.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.