Monkey patches React to notify about avoidable re-renders by tracking pure components and hooks.
—
Built-in component tracking and notification system that provides detailed analysis of re-render causes and patterns.
Complete information about a component re-render event, provided to notifiers:
interface UpdateInfo {
/** The component that re-rendered */
Component: React.Component;
/** Display name of the component */
displayName: string;
/** Previous props object */
prevProps: any;
/** Previous state object */
prevState: any;
/** Next props object */
nextProps: any;
/** Next state object */
nextState: any;
/** Previous hook result (for hook updates) */
prevHookResult: any;
/** Next hook result (for hook updates) */
nextHookResult: any;
/** Detailed reason for the update */
reason: ReasonForUpdate;
/** WDYR options used for this tracking */
options: WhyDidYouRenderOptions;
/** Name of the hook (if this is a hook update) */
hookName?: string;
/** Previous owner component (for owner tracking) */
prevOwner?: React.Component;
/** Next owner component (for owner tracking) */
nextOwner?: React.Component;
}Detailed analysis of why a component re-rendered:
interface ReasonForUpdate {
/** Array of hook-related changes that caused the re-render */
hookDifferences: HookDifference[];
/** Whether props changes contributed to the re-render */
propsDifferences: boolean;
/** Whether state changes contributed to the re-render */
stateDifferences: boolean;
/** Owner component differences (when logOwnerReasons is enabled) */
ownerDifferences?: {
propsDifferences?: HookDifference[];
stateDifferences?: HookDifference[];
hookDifferences?: Array<{
hookName: string;
differences: HookDifference[];
}>;
};
}
interface HookDifference {
/** Path string to the changed value (e.g., "0" for useState, "items[2].name" for nested) */
pathString: string;
/** Type of difference detected (e.g., "different", "deepEquals") */
diffType: string;
/** Previous value before the change */
prevValue: any;
/** Next value after the change */
nextValue: any;
}Built-in notification handler that logs re-render information to the console:
/**
* Default notification handler for component re-render events
* Logs detailed information about why the component re-rendered
* @param updateInfo - Complete information about the component update
*/
function defaultNotifier(updateInfo: UpdateInfo): void;Usage Example:
import whyDidYouRender from '@welldone-software/why-did-you-render';
// The default notifier is used automatically
whyDidYouRender(React, {
trackAllPureComponents: true
});
// Access the default notifier directly if needed
console.log(whyDidYouRender.defaultNotifier);Create custom notification handlers for specialized logging or integration with monitoring systems:
/**
* Custom notifier function type
* @param updateInfo - Information about the component update
*/
type Notifier = (updateInfo: UpdateInfo) => void;Usage Examples:
// Custom notifier that sends data to analytics
const analyticsNotifier = (updateInfo) => {
analytics.track('component_rerender', {
componentName: updateInfo.displayName,
hasPropsChanges: updateInfo.reason.propsDifferences,
hasStateChanges: updateInfo.reason.stateDifferences,
hookChanges: updateInfo.reason.hookDifferences.length
});
};
whyDidYouRender(React, {
trackAllPureComponents: true,
notifier: analyticsNotifier
});
// Custom notifier that filters by component type
const filteredNotifier = (updateInfo) => {
if (updateInfo.displayName.startsWith('Heavy')) {
console.warn(`Performance concern: ${updateInfo.displayName} re-rendered`);
whyDidYouRender.defaultNotifier(updateInfo);
}
};
whyDidYouRender(React, {
notifier: filteredNotifier
});Utility for getting meaningful component names for logging:
/**
* Gets a display name for a component
* @param Component - React component to get name for
* @returns String display name
*/
function getDisplayName(Component: React.ComponentType): string;Utilities for identifying different types of React components:
/**
* Determines if a component is a React.memo wrapped component
* @param Component - Component to check
* @returns Boolean indicating if it's a memo component
*/
function isMemoComponent(Component: any): boolean;
/**
* Determines if a component is a React.forwardRef wrapped component
* @param Component - Component to check
* @returns Boolean indicating if it's a forwardRef component
*/
function isForwardRefComponent(Component: any): boolean;
/**
* Determines if a component is a React class component
* @param Component - Component to check
* @returns Boolean indicating if it's a class component
*/
function isReactClassComponent(Component: any): boolean;Function to determine if a component should be tracked based on configuration:
/**
* Determines if a component should be tracked for re-renders
* @param Component - Component to evaluate
* @param options - Tracking context options
* @returns Boolean indicating if component should be tracked
*/
function shouldTrack(
Component: React.ComponentType,
options: { isHookChange?: boolean }
): boolean;
/**
* Gets default props for a component (handles both function and class components)
* @param Component - Component to get default props for
* @returns Default props object or empty object
*/
function getDefaultProps(Component: React.ComponentType): object;
/**
* Gets update information for a component re-render
* @param options - Component and render information
* @returns Structured update information for notifiers
*/
function getUpdateInfo(options: {
Component: React.ComponentType;
displayName: string;
hookName?: string;
prevHookResult?: any;
nextHookResult?: any;
prevProps?: any;
nextProps?: any;
prevState?: any;
nextState?: any;
}): UpdateInfo;When logOwnerReasons is enabled, the library tracks which parent components cause child re-renders:
/**
* Stores owner component data for render tracking
* @param element - React element to store owner data for
*/
function storeOwnerData(element: React.Element): void;
/**
* Gets the current owner component during rendering
* @returns Current owner component or null
*/
function getCurrentOwner(): React.Component | null;Built-in hot reload detection to prevent false positives during development:
/**
* Creates a notifier that handles hot reload scenarios
* @param hotReloadBufferMs - Buffer time in milliseconds
* @returns Configured notifier function
*/
function createDefaultNotifier(hotReloadBufferMs?: number): Notifier;Usage Example:
whyDidYouRender(React, {
hotReloadBufferMs: 1000, // 1 second buffer
trackAllPureComponents: true
});Customize the appearance of console output:
interface ConsoleOptions {
/** Color for component names (default: '#058') */
titleColor?: string;
/** Color for diff property names (default: 'blue') */
diffNameColor?: string;
/** Color for diff property paths (default: 'red') */
diffPathColor?: string;
/** Background color for text (default: 'white') */
textBackgroundColor?: string;
/** Use console.log instead of console.group (default: false) */
onlyLogs?: boolean;
/** Use console.groupCollapsed (default: false) */
collapseGroups?: boolean;
}Usage Example:
whyDidYouRender(React, {
trackAllPureComponents: true,
titleColor: '#ff6b6b',
diffNameColor: '#4ecdc4',
diffPathColor: '#45b7d1',
collapseGroups: true
});Install with Tessl CLI
npx tessl i tessl/npm-welldone-software--why-did-you-render@10.0.1