Open-Source Wikis

/

React

/

How to contribute

/

Debugging

facebook/react

Debugging

How to actually figure out what the runtime is doing when a bug doesn't reproduce on the first try.

Where to put a console.log

The reconciler runs everywhere, so generic logs drown in noise. The places that are usually worth instrumenting:

  • beginWork and completeWork for "what is the reconciler doing right now": packages/react-reconciler/src/ReactFiberBeginWork.js and ReactFiberCompleteWork.js.
  • commitMutationEffects, commitLayoutEffects, flushPassiveEffects for "what is happening at commit": packages/react-reconciler/src/ReactFiberCommitWork.js.
  • The renderRootSync / renderRootConcurrent entry points in ReactFiberWorkLoop.js.
  • dispatchSetStateInternal / dispatchReducerAction in ReactFiberHooks.js for "who is causing this re-render".
  • For SSR, processSegmentForFizz and the encoder helpers in packages/react-server/src/ReactFizzServer.js.
  • For Flight, the serializeModel / serializeLazyValue branches in packages/react-server/src/ReactFlightServer.js.

Most files have __DEV__ blocks already. The Rollup replace plugin strips them in production. Keep new debug-only logs inside if (__DEV__).

React DevTools

packages/react-devtools-extensions/ is the browser extension; packages/react-devtools/ is the standalone Electron app; packages/react-devtools-shared/ is the shared core. To rebuild and load:

yarn build-for-devtools-dev
cd packages/react-devtools-extensions
yarn build:chrome:local        # or :firefox:local
# Load build/chrome/ as an unpacked extension

The DevTools "Components" panel reads the fiber tree via the hook installed by packages/react-devtools-shared/src/backend/fiber/renderer.js — that's the single most-edited file in the DevTools tree, because it has to keep up with every new fiber tag and every hook change. The Profiler panel reads commit data via packages/react-reconciler/src/ReactFiberDevToolsHook.js.

The __DEV__-only error logger

packages/react-reconciler/src/ReactFiberErrorLogger.js is what produces the "Error: Hello, undefined! ..." red console message during a render error. If you want to see the unboxed error in development, add a temporary console.error(error) there.

ReactFiberCallUserSpace.js is where React calls into user code (render functions, effects, etc.) and is wrapped to give better stack traces — useful when you want to check what was on the stack when an error fired.

Inspecting fibers

For one-off "what does this fiber tree look like" debugging:

// In any breakpoint inside the reconciler:
function dumpFiber(fiber, depth = 0) {
  if (!fiber) return;
  console.log(
    ' '.repeat(depth * 2) +
      fiber.type?.name +
      ' tag=' +
      fiber.tag +
      ' lanes=' +
      fiber.lanes
  );
  dumpFiber(fiber.child, depth + 1);
  dumpFiber(fiber.sibling, depth);
}

fiber.tag values are listed in packages/react-reconciler/src/ReactWorkTags.js. fiber.flags in ReactFiberFlags.js. fiber.lanes are 31-bit lane sets defined in ReactFiberLane.js.

Lane / scheduling debugging

packages/react-reconciler/src/ReactFiberLane.js ships a formatLanes() helper for converting a bitmask to a readable string. It's only available in dev. Use it like:

console.log('about to render', formatLanes(workInProgressRootRenderLanes));

To see the work loop's full schedule, set the global ReactDebugCurrentFrame (which exposes the in-flight fiber) and break in ReactFiberWorkLoop.js's performUnitOfWork.

Performance debugging

The reconciler emits Performance API marks in dev when enableSchedulingProfiler is on. Read them with the React Profiler tab in DevTools, or directly in the browser's Performance panel — there's a "React Performance Tracks" view introduced in React 19.2 (see packages/react-reconciler/src/ReactFiberPerformanceTrack.js).

For really fine-grained timing, the test harness lets you advance the mock scheduler by exact amounts (Scheduler.unstable_advanceTime(N)) which is more reproducible than wall time.

SSR / Flight debugging

When debugging streaming SSR or Server Components:

  1. Reproduce in a fixture under fixtures/flight/ or fixtures/ssr2/. They're tiny, real React apps.
  2. Tail the actual stream output (curl -N http://localhost:3000/) — you'll see Flight rows appear one at a time.
  3. The wire format is documented inline in packages/react-server/src/ReactFlightServer.js. Each row is <id>:<tag><payload>\n. Knowing the tags (I for module, J for client reference, H for hint, D for done, …) is enough to read it by hand.

Common pitfalls

  • act swallows errors. If your test passes when it shouldn't, your error is being silently caught by act. Use expect(act(...)).rejects patterns or directly inspect Scheduler.log.
  • __DEV__ flags differ in tests vs source. Source uses __DEV__, but compiled bundles in --build mode have __DEV__ replaced. A bug that only shows up in production usually means a logic error inside a __DEV__ block was masking it.
  • Channel mismatch. If a behavior reproduces in react@experimental but not react@latest, you're probably looking at a flag-gated diff. Check packages/shared/forks/ReactFeatureFlags.*.js for the flag's value per channel.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Debugging – React wiki | Factory