-
Notifications
You must be signed in to change notification settings - Fork 0
useRenderReason
A powerful, zero-overhead debugging hook that diagnoses exactly why your component just re-rendered. It catches wasted renders, deeply nested reference changes, anonymous function identities, and suspiciously frequent render loops.
Tip
This hook is zero-overhead in production. When process.env.NODE_ENV === 'production', it automatically strips its tracking logic, ensuring your app stays incredibly fast. You can leave it in your code safely.
Track standard props to see why a child component is updating.
import { useRenderReason } from "react-hook-lab";
function ProductCard({ id, name, price }: { id: string, name: string, price: number }) {
// It will print a beautiful, color-coded diagnostic log to the console
// every time this component re-renders!
useRenderReason("ProductCard", { id, name, price });
return <div>{name} - ${price}</div>;
}Diagnosing function identity changes (a common cause of broken memoization).
import React, { memo } from "react";
import { useRenderReason } from "react-hook-lab";
const OptimizedButton = memo(({ onClick }: { onClick: () => void }) => {
useRenderReason("OptimizedButton", { onClick });
return <button onClick={onClick}>Click Me</button>;
});
function Parent() {
// BAD! This inline function causes OptimizedButton to re-render EVERY time.
// useRenderReason will flag this as "function-reference-changed"!
return <OptimizedButton onClick={() => console.log("Clicked")} />;
}Customizing the tracking options, ignoring specific noisy props, and intercepting the output to build a custom DevTools UI.
import { useRenderReason } from "react-hook-lab";
function ComplexDashboard({ data, user, bigRef, themeContext }) {
const info = useRenderReason("Dashboard", { data, user, bigRef }, {
deep: true, // Dig into object properties
ignore: ["bigRef"], // Don't track changes to this specific key
warnThreshold: 5, // Warn if it renders 5 times...
warnWindowMs: 500, // ...within half a second!
logToConsole: false, // Disable console spam
onRender: (diagnostic) => {
// Send the diagnostic data to an external monitoring tool
myAnalytics.sendRenderReport(diagnostic);
}
});
return <div>{/* Dashboard UI */}</div>;
}-
componentName- Type:
string - Description: An identifier printed in the logs to help you find the component.
- Type:
-
watched- Type:
Record<string, unknown> - Description: An object containing the props, state, or variables you want to track.
- Type:
-
options(Optional)- Type:
UseRenderReasonOptions - Description: Configuration object to adjust deep comparison, warnings, and logging.
- Type:
export interface UseRenderReasonOptions {
deep?: boolean; // Deep-compare objects. Default: true.
ignore?: string[]; // Keys to skip tracking.
warnThreshold?: number; // Render count limit. Default: 10.
warnWindowMs?: number; // Window limit. Default: 1000ms.
logToConsole?: boolean; // Auto-log to console. Default: true in dev.
onRender?: (info: RenderReasonInfo) => void;
trackContexts?: boolean; // Auto-detect Context changes. Default: true.
}Returns RenderReasonInfo (which is also passed to onRender):
export interface RenderReasonInfo {
componentName: string;
renderCount: number;
msSinceLastRender: number | null;
changes: PropChange[]; // Array of exactly what changed and why
isWastedRender: boolean; // True if NOTHING changed
isSuspiciouslyFrequent: boolean;// True if caught in a render loop
}useRenderReason works by caching the watched object in a useRef and executing a differential comparison on the next render phase. It categorizes changes into four distinct types:
-
primitive-changed: A genuine value change (e.g.1to2). -
function-reference-changed: A function changed identity (usually an un-memoized arrow function). -
reference-changed-value-same: An object or array changed identity, but its contents are deeply identical (usually an inline array literal[]breakingReact.memo). -
reference-changed-value-changed: An object changed identity and its internal data actually changed.
Context Tracking: If trackContexts is true (default), the hook utilizes undocumented internal React Fiber APIs to peek at the component's dependencies.firstContext. It extracts and compares the actual React Contexts the component is subscribed to, surfacing invisible context renders without you having to manually track them!
Built with ❤️ by Saurav-TB-Pandey. | Report an Issue | NPM