{"record":{"id":"fc6bd707cebb081e","repo":"facebook/react","slug":"already-flushing-work","errorCode":null,"errorMessage":"Already flushing work.","messagePattern":"Already flushing work\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/scheduler/src/forks/SchedulerMock.js","lineNumber":506,"sourceCode":"function reset() {\n  if (isFlushing) {\n    throw new Error('Cannot reset while already flushing work.');\n  }\n  currentMockTime = 0;\n  scheduledCallback = null;\n  scheduledTimeout = null;\n  timeoutTime = -1;\n  yieldedValues = null;\n  expectedNumberOfYields = -1;\n  didStop = false;\n  isFlushing = false;\n  needsPaint = false;\n}\n\n// Should only be used via an assertion helper that inspects the yielded values.\nfunction unstable_flushNumberOfYields(count: number): void {\n  if (isFlushing) {\n    throw new Error('Already flushing work.');\n  }\n  if (scheduledCallback !== null) {\n    const cb = scheduledCallback;\n    expectedNumberOfYields = count;\n    isFlushing = true;\n    try {\n      let hasMoreWork = true;\n      do {\n        hasMoreWork = cb(true, currentMockTime);\n      } while (hasMoreWork && !didStop);\n      if (!hasMoreWork) {\n        scheduledCallback = null;\n      }\n    } finally {\n      expectedNumberOfYields = -1;\n      didStop = false;\n      isFlushing = false;\n    }","sourceCodeStart":488,"sourceCodeEnd":524,"githubUrl":"https://github.com/facebook/react/blob/eafeac097ba51e1eab809c07102126bd5f8e5425/packages/scheduler/src/forks/SchedulerMock.js#L488-L524","documentation":"SchedulerMock's unstable_flushNumberOfYields(count) drives the queued scheduler callback in a loop until it has produced `count` yielded values (it powers assertion helpers like toFlushAndYieldThrough). Like every flush helper it sets the module-level isFlushing flag for the duration, and it throws 'Already flushing work.' on entry if another flush helper is still running, because the mock's single shared state (expectedNumberOfYields, didStop, yieldedValues) cannot back two interleaved flushes.","triggerScenarios":"Invoking Scheduler.unstable_flushNumberOfYields(n) — directly or via expect(Scheduler).toFlushAndYieldThrough([...]) — from inside a callback, effect, or assertion helper that is itself being flushed by unstable_flushAll(), unstable_flushAllWithoutAsserting(), or another flush helper.","commonSituations":"Custom test helpers that 'ensure flushed' state by flushing at the start; component effects that flush scheduler work during act(); copy-pasted test-utils calling toFlush* helpers defensively inside teardown; nested test-runner hooks.","solutions":["Make flush helper calls strictly sequential — each toFlush*/unstable_flush* call must fully return before the next one starts.","Move the nested flush out of the scheduled callback or effect: run the outer flush to completion, then assert on remaining work.","Remove defensive 'flush just in case' calls from shared test-utils that may execute inside an active flush.","If React work itself triggers the nested flush, split the test into two top-level flushes instead of nesting."],"exampleFix":"// before\nScheduler.scheduleCallback(NormalPriority, () => {\n  Scheduler.log('a');\n  Scheduler.unstable_flushNumberOfYields(1); // throws: already flushing\n});\nScheduler.unstable_flushAll();\n\n// after\nScheduler.scheduleCallback(NormalPriority, () => {\n  Scheduler.log('a');\n});\nexpect(Scheduler).toFlushAndYieldThrough(['a']); // one flush helper at a time","handlingStrategy":"validation","validationCode":"// Guard every flush entry point with your own flag.\nlet flushing = false;\nfunction safeFlushNumberOfYields(Scheduler: any, count: number) {\n  if (flushing) return; // skip instead of throwing\n  flushing = true;\n  try {\n    Scheduler.unstable_flushNumberOfYields(count);\n  } finally {\n    flushing = false;\n  }\n}","typeGuard":null,"tryCatchPattern":"try {\n  Scheduler.unstable_flushNumberOfYields(2);\n} catch (e) {\n  if (e instanceof Error && e.message.includes('Already flushing work')) {\n    // a flush is already on the stack; move this call after it returns\n  } else {\n    throw e;\n  }\n}","preventionTips":["Call flush helpers only from the top level of a test, never from task callbacks or effects.","One assertion helper (toFlush*) per flush; do not chain them re-entrantly.","Keep shared test-utils free of defensive 'flush just in case' calls."],"tags":["scheduler","testing","mock","reentrancy"],"backgroundTag":"operation-already-in-progress","analyzedSha":"eafeac097ba51e1eab809c07102126bd5f8e5425","analyzedAt":"2026-08-21T22:01:08.818Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}