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

  1. Restructure so batches are not nested — move the inner callback's work into the outer batch
  2. Only batch at the top level of your update logic, not inside subscribers or other batched callbacks
  3. Guard with a flag in your own code if multiple code paths may batch concurrently
  4. 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

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


AI-assisted analysis of facebook/relay@668b1b85e0 (2026-09-02). Data as JSON: /api/errors/02dc12f20c365575. Report an issue: GitHub.