Implementing Iteration Limits in MobX
MobX does not currently provide a maxIterations configuration option in its reaction API. To limit the number of times a reaction executes, you must implement a counter and manually invoke the disposer returned by the reaction function.
Recommended Implementation
To avoid boilerplate and memory leaks, encapsulate the counting logic within a wrapper function. This ensures the disposer is called immediately upon reaching the threshold.
import { reaction } from 'mobx';
function createLimitedReaction(dataExpr, effectExpr, maxIterations) {
let count = 0;
const disposer = reaction(
dataExpr,
(data, previous) => {
count++;
if (count >= maxIterations) {
console.warn('Max iterations reached; disposing reaction.');
disposer();
}
effectExpr(data, previous);
}
);
return disposer;
}
Analysis of Proposed Behavior
When considering how a native maxIterations flag should behave, the following logic aligns with MobX's existing architectural patterns:
Automatic Disposal
The disposer should be invoked automatically. In MobX, reactions are intended to be long-lived unless explicitly stopped. A maxIterations limit transforms a continuous reaction into a finite one; therefore, failing to dispose of the reaction would leave a dormant observer in memory, leading to leaks.
Error Handling and the Limit
Errors occurring inside the reaction should still count toward the limit. If a reaction fails but continues to trigger due to state changes, it can create a "crash loop" that consumes CPU cycles without ever succeeding. Counting the attempt, regardless of the outcome, ensures the system eventually halts the failing process.
Distinction from Recursive Limits
It is important to distinguish a user-defined iteration limit from MobX's internal safety mechanisms. MobX has a hard-coded limit on recursive updates (where a reaction triggers itself synchronously). When this is hit, MobX throws a "Too many recursive updates" error to prevent a stack overflow. A maxIterations feature would instead manage asynchronous or sequential updates over time.
Verification Step
To verify if your reaction is looping unexpectedly, wrap the state mutation inside the reaction with the untracked utility. If the loop stops, the mutation was triggering the reaction's own dependency tracker.
Diagnostic Detail: Are these iterations occurring synchronously (recursive) or asynchronously (event-driven)? This determines whether you need a manual counter or the untracked utility.