Skip to content
LogoLogo

Debug / Observability

Hooks relying on private, non-semver-guaranteed APIs (react-dom internals, mostly) carry an Unstable marker in the hook's own name — useUnstableFiber, not a separate module — so the risk travels with every import and autocomplete hit, not just a path a reader might skip.

useUnstableRenderCount

The render count for the calling component instance — 1 on mount, incrementing by one on every subsequent render. Always 0 in production builds.

const renderCount = useUnstableRenderCount();
console.log(`rendered ${renderCount} times`);

useUnstableWhyDidYouUpdate

Logs which of props' keys changed to cause the current render — console.logs a name-labeled table of { from, to } per changed key, or nothing when no key changed (a render caused by state/context rather than these particular props). Does nothing on the mount render, since there's no previous props to diff against, or in production builds.

function UserCard(props: { name: string; age: number }) {
  useUnstableWhyDidYouUpdate("UserCard", props);
  return <div>{props.name}</div>;
}

useUnstableIsFirstRender

true only on the mount render, false on every render after. Always false in production builds.

const isFirstRender = useUnstableIsFirstRender();
if (isFirstRender) console.log("mounted");

useUnstableRenderReason

Classifies why the ref'd component just re-rendered — "mount", "props", "state" (a useState/useReducer value changed), "context" (a read useContext() value changed), or "parent" (none of the above changed, so the parent re-rendered this component without a locally-observable cause). Computed in an effect, after commit, so reason updates one render behind the change that caused it.

Call it as the first hook in the component — state detection skips this hook's own internal hooks by count when walking the Fiber hook list, so a stateful hook called before it would be miscounted as this hook's own.

const { ref, reason } = useUnstableRenderReason<HTMLDivElement>();
return <div ref={ref}>{reason}</div>;

useUnstableFiber

Returns the nearest Fiber node for a ref'd DOM element, via react-dom's private __reactFiber$<id> DOM pointer — walks up to the nearest function-component ancestor when found, else the host (DOM) fiber itself. No public API for this — it's the same private tree React DevTools itself walks. fiber is null until ref attaches to a mounted element, and stays null (rather than throwing) on an unrecognized internal shape or in production builds.

const { ref, fiber } = useUnstableFiber<HTMLDivElement>();
return <div ref={ref}>{typeof fiber?.type === "function" ? fiber.type.name : "?"}</div>;

useUnstableRenderDuration

Wraps React's <Profiler> onRender timing as a hook — pass onRender to a <Profiler> wrapping the subtree to measure; samples accumulates each render's { id, phase, actualDuration, baseDuration, startTime, commitTime }, capped at the last limit (default 20). Records nothing in production builds.

const { onRender, last } = useUnstableRenderDuration();
return (
  <Profiler id="Sidebar" onRender={onRender}>
    <Sidebar />
  </Profiler>
);

useUnstableOwnerStack

Wraps React 19's captureOwnerStack debug API — call captureOwnerStack() during an event handler or effect to get the JSX "owner" stack (which component rendered which), the same trace React's own dev warnings use. supported: false where the export doesn't exist (React < 19, or a production build — React's own captureOwnerStack already returns null there, which this wrapper surfaces as undefined).

const { captureOwnerStack: capture, supported } = useUnstableOwnerStack();
const handleError = () => console.error(supported ? capture() : "unavailable");

See Also