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");