facebook/relay · error
RelayModernStore: Cannot batch updates while already batchin
Error message
RelayModernStore: Cannot batch updates while already batching updates.
What it means
RelayModernStore only supports a single active batch: experimental_batchUpdates sets this._batch and throws if it is already non-null, because nesting batches could incorrectly resolve cross-operation Suspense and produce inconsistent public-record notifications.
Source
Thrown at packages/relay-runtime/store/RelayModernStore.js:263
try {
this._resolverCache.batchLiveStateUpdates(callback);
} finally {
if (this.__log != null) {
this.__log({name: 'liveresolver.batch.end'});
}
}
}
/**
* Batch multiple store updates into a single notification pass.
*
* Fragments will still correctly Suspend on their own parent query during
* a batch. However, cross-operation Suspense (a fragment suspending on a
* *different* in-flight operation) may not work correctly for operations
* that complete during the batch.
*/
experimental_batchUpdates(callback: () => void): void {
if (this._batch != null) {
throw new Error(
'RelayModernStore: Cannot batch updates while already batching updates.',
);
}
const log = this.__log;
if (log != null) {
log({name: 'store.batch.start'});
}
const batch: Batch = {sourceOperations: [], invalidateStore: false};
this._batch = batch;
try {
callback();
} finally {
this._batch = null;
this.notify(undefined, batch.invalidateStore);
for (const sourceOperation of batch.sourceOperations) {
this._recordSourceOperation(sourceOperation);
}View on GitHub (pinned to 668b1b85e0)
Solutions
- Restructure so batches are not nested — move the inner callback's work into the outer batch
- Only batch at the top level of your update logic, not inside subscribers or other batched callbacks
- Guard with a flag in your own code if multiple code paths may batch concurrently
- Use environment.execute (which handles batching internally via RelayDefaultHandlerProvider commit paths) rather than manual nested batching
Example fix
// before
store.experimental_batchUpdates(() => {
store.experimental_batchUpdates(() => updateB());
});
// after
store.experimental_batchUpdates(() => {
updateA();
updateB();
}); Defensive patterns
Strategy: try-catch
Validate before calling
if (store._batch != null) {
callback(); // already batching; run inline
} else {
store.experimental_batchUpdates(callback);
} Type guard
null
Try / catch
try {
store.experimental_batchUpdates(cb);
} catch (e) {
if (String(e.message).includes('Cannot batch updates while already batching')) {
cb(); // or merge into the outer batch
} else throw e;
} Prevention
- Only call batchUpdates at top-level app code, never inside subscribers or other batches
- Centralize batching in one utility with a re-entrancy guard
- Avoid batching inside event handlers triggered during commits
- Treat batchUpdates as single-owner API in team conventions
When it happens
Trigger: Calling store.experimental_batchUpdates(cb) from inside another experimental_batchUpdates callback — e.g. two library routines both batching during a commit, or an event handler invoked within a batched callback issuing another batched update.
Common situations: Composing libraries or middlewares that each call batchUpdates; scheduling batched commits from within store subscribers fired during a batch; React concurrent callbacks overlapping with manual batching.
Related errors
- Did not find item with data id ${__id} in the store.
- NormalizationEngine: Expected record `${parentID}` to have f
- NormalizationEngine: Expected record `${parentID}` to exist.
- BabelPluginRelay: Expected plugin context to include "types"
- BabelPluginRelay: Expected exactly one definition per graphq
AI-assisted analysis of facebook/relay@668b1b85e0 (2026-09-02).
Data as JSON: /api/errors/02dc12f20c365575.
Report an issue: GitHub.