Skip to main content

useWhyDidYouUpdate

Discover why your component re-rendered by tracking prop and state changes.

Overview

useWhyDidYouUpdate is a debugging hook that logs which props changed between renders, helping you identify unnecessary re-renders and optimize performance.

API

useWhyDidYouUpdate(componentName: string, props: Record<string, any>);

Parameters:

  • componentName: Name to display in console logs
  • props: Object containing props to track

Basic Example

import { useWhyDidYouUpdate } from 'react-dev-debugger';

function ExpensiveComponent({ user, settings, data }) {
useWhyDidYouUpdate('ExpensiveComponent', { user, settings, data });

// Your component logic
return <div>...</div>;
}

Live Playground

Component Props

Primitive Values
useWhyDidYouUpdate('MyComponent', {
  count: 0,
  text: "",
  enabled: true
});
Callbacks & Objects
// ✅ Stable - wrapped in useCallback
const stableCallback = useCallback(() => {
  console.log('stable');
}, []);

// ❌ Unstable - new function each render
const unstableCallback = () => {
  console.log('unstable');
};

// ✅ Stable - wrapped in useMemo
const memoizedConfig = useMemo(() => ({
  theme: 'dark'
}), []);

// ❌ Unstable - new object each render
const freshConfig = {
  theme: 'dark'
};

Why Did You Update?

Change a prop to see what caused the re-render

How to use:

  • Change props to trigger updates
  • Green badges = stable props
  • Red badges = changed props
  • Functions/objects are always new unless memoized

Advanced Examples

React.memo Component

interface UserCardProps {
user: User;
onEdit: () => void;
theme: Theme;
}

const UserCard = React.memo(function UserCard({ user, onEdit, theme }: UserCardProps) {
useWhyDidYouUpdate('UserCard', { user, onEdit, theme });

return (
<div className={`user-card theme-${theme}`}>
<h3>{user.name}</h3>
<button onClick={onEdit}>Edit</button>
</div>
);
});

With useMemo

function DataTable({ data, filters, sort }: DataTableProps) {
useWhyDidYouUpdate('DataTable', { data, filters, sort });

const filteredData = useMemo(() => {
return data.filter(item =>
filters.every(f => f.test(item))
);
}, [data, filters]);

const sortedData = useMemo(() => {
return [...filteredData].sort(sort);
}, [filteredData, sort]);

return (
<table>
{sortedData.map(row => (
<TableRow key={row.id} data={row} />
))}
</table>
);
}

Context Consumer

function ThemedButton() {
const theme = useContext(ThemeContext);
const auth = useContext(AuthContext);

useWhyDidYouUpdate('ThemedButton', { theme, auth });

return (
<button className={theme.buttonClass}>
{auth.user ? 'Logout' : 'Login'}
</button>
);
}

Form Component

function FormField({ 
value,
onChange,
validator,
error
}: FormFieldProps) {
useWhyDidYouUpdate('FormField', {
value,
onChange,
validator,
error
});

const [touched, setTouched] = useState(false);

return (
<div>
<input
value={value}
onChange={onChange}
onBlur={() => setTouched(true)}
/>
{touched && error && <span>{error}</span>}
</div>
);
}

List Component

function TodoList({ todos, filter, onToggle }: TodoListProps) {
useWhyDidYouUpdate('TodoList', { todos, filter, onToggle });

const visibleTodos = useMemo(() => {
switch (filter) {
case 'active':
return todos.filter(t => !t.completed);
case 'completed':
return todos.filter(t => t.completed);
default:
return todos;
}
}, [todos, filter]);

return (
<ul>
{visibleTodos.map(todo => (
<TodoItem
key={todo.id}
todo={todo}
onToggle={onToggle}
/>
))}
</ul>
);
}

Debugging Features

Console Output

The hook logs changes to the console:

[why-did-you-update] ExpensiveComponent
┌─────────┬──────────┬──────────┐
│ (index) │ from │ to │
├─────────┼──────────┼──────────┤
│ count │ 5 │ 6 │
│ user │ {id: 1} │ {id: 1} │
└─────────┴──────────┴──────────┘

Prop Change Detection

Identifies which props changed:

  • Primitive value changes
  • Object reference changes
  • Function reference changes
  • Array modifications

Performance Insights

  • Spot unnecessary re-renders
  • Identify prop drilling issues
  • Find missing memoization
  • Track callback stability

Best Practices

✅ Do's

  • Use in development only: Remove in production builds
  • Track relevant props: Don't track every prop
  • Name components clearly: Use descriptive names
  • Combine with React DevTools: Use both for full picture

❌ Don'ts

  • Don't leave in production: Performance overhead
  • Don't track functions without checking: May always be new
  • Don't ignore warnings: Each indicates potential issue

Common Patterns

Identify Unstable Callbacks

// ❌ Bad - creates new function every render
function Parent() {
return <Child onClick={() => console.log('clicked')} />;
}

// ✅ Good - memoized callback
function Parent() {
const handleClick = useCallback(() => {
console.log('clicked');
}, []);

return <Child onClick={handleClick} />;
}

Find Object Reference Changes

// ❌ Bad - creates new object every render
function Parent() {
return <Child config={{ theme: 'dark' }} />;
}

// ✅ Good - memoized object
function Parent() {
const config = useMemo(() => ({ theme: 'dark' }), []);
return <Child config={config} />;
}

Detect Missing Dependencies

function Component({ userId, onUpdate }) {
useWhyDidYouUpdate('Component', { userId, onUpdate });

useEffect(() => {
// If this effect runs too often, check why-did-you-update output
fetchUser(userId).then(onUpdate);
}, [userId, onUpdate]);

return <div>...</div>;
}

TypeScript Support

Full type safety:

interface Props {
user: User;
count: number;
onUpdate: (id: string) => void;
}

function MyComponent({ user, count, onUpdate }: Props) {
// Type-safe prop tracking
useWhyDidYouUpdate<Props>('MyComponent', { user, count, onUpdate });

return <div>...</div>;
}

Development Only

Remove in production:

function Component(props: Props) {
if (process.env.NODE_ENV === 'development') {
useWhyDidYouUpdate('Component', props);
}

return <div>...</div>;
}

Next Steps