Using MobX reaction to debounce API calls in a search input
Learn how MobX’s reaction primitive can debounce API calls from a search input, reducing unnecessary requests while keeping the UI responsive.
29 Sept 2025, 02:15 UTC

input event, a naïve effect that calls the search API runs for every character typed. This generates unnecessary network traffic and can overwhelm the backend.
Thesis: use a MobX reaction to derive a debounced value and trigger the effect only when that value stabilizes
MobX provides three reaction primitives. reaction lets you track a data‑producing function (the derivation) and run an side‑effect whenever its output changes. By wrapping the derivation in a debounce, the effect fires only after the user pauses typing.
Worked example: observable query, debounced reaction, and cleanup
import { observable, action, reaction } from 'mobx';
class SearchStore {
@observable query = '';
@observable results = [];
@observable loading = false;
@action
setQuery(value) {
this.query = value;
}
constructor() {
// Derivation: return the current query trimmed
const disposer = reaction(
() => this.query.trim(), // data‑producing function
(trimmedQuery) => { // effect
if (!trimmedQuery) {
this.results = [];
this.loading = false;
return;
}
this.loading = true;
// Simulated API call – replace with real fetch
fetch(`/api/search?q=${encodeURIComponent(trimmedQuery)}`)
.then(r => r.json())
.then(data => {
this.results = data;
this.loading = false;
})
.catch(err => {
console.error(err);
this.loading = false;
});
},
{ delay: 300 } // debounce option (MobX 6)
);
// Dispose when the store is no longer needed (e.g., component unmount)
this._reactionDisposer = disposer;
}
// Call this when the component unmounts
dispose() {
if (this._reactionDisposer) {
this._reactionDisposer();
}
}
}
// Usage in a React component (simplified)
// store.setQuery(e.target.value)} />
Explanation:
- The first argument to
reactionreturns the derived value (this.query.trim()). - The second argument runs the expensive operation only when that derived value changes.
- MobX 6’s
delayoption debounces the derivation, so the effect waits 300 ms after the last change. - The disposer returned by
reactionis stored and called later to prevent memory leaks.
Trade‑off and limitation
Reactions add a small runtime overhead because MobX must track the derivation and schedule the effect. Over‑using them can hide the flow of data, making debugging harder; if the effect mutates an observable that the derivation also reads, MobX’s strict mode will throw an infinite‑loop error. In server‑side rendering you must manually dispose the reaction before sending the HTML, otherwise the disposer is never called and memory can leak.
How to verify it works
- Copy the
SearchStoreinto a project with MobX 6 installed. - Render an input bound to
store.queryand observe the console. - You should see the fetch request (or its simulated log) fire only after you stop typing for ~300 ms.
- Open MobX devtools (
mobx-react-devtools) and watch the reaction being triggered and disposed as the component mounts/unmounts. - Remove the
delayoption or set it to 0 and confirm the API call now fires on every keystroke, proving the reaction’s debounce effect.
Give it a try, check the network tab, and you’ll see fewer requests while the UI stays responsive.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.