What Causes a React Component to Re-render?
I was debugging a performance issue in my React app when I realized I didn’t fully understand what triggers component re-renders. I thought I knew, but when I tried to explain it to a colleague, I got confused. So I dug into the React documentation and tested different scenarios.
Here’s what I found: React components re-render for exactly four reasons. Once you understand these triggers, you can write more predictable and efficient code.
The Four Re-render Triggers
React components re-render when:
- Their state changes (via
useStateoruseReducer) - Their parent component re-renders
- Their props change
- Their context value changes
Let me show you each one with code examples.
1. State Changes
The most obvious trigger. When you call a state setter function, React schedules a re-render.
import { useState } from 'react';
function Counter() { const [count, setCount] = useState(0);
console.log('Counter re-rendered');
return ( <div> <p>Count: {count}</p> <button onClick={() => setCount(count + 1)}> Increment </button> </div> );}Every time I click the button, setCount is called, and the component re-renders. I can see the “Counter re-rendered” message in the console each time.
Important note: Even if I call setCount(0) when the count is already 0, React will still schedule a re-render (though it might bail out of rendering if the value hasn’t changed in some cases).
2. Parent Re-renders
This one surprised me at first. When a parent component re-renders, all of its children re-render too, regardless of whether their props changed.
function Child({ name }) { console.log('Child re-rendered'); return <div>Hello, {name}</div>;}
function Parent() { const [count, setCount] = useState(0);
console.log('Parent re-rendered');
return ( <div> <Child name="Alice" /> <button onClick={() => setCount(count + 1)}> Increment Parent Count </button> </div> );}When I click the button in the parent, both “Parent re-rendered” and “Child re-rendered” appear in the console. The child’s props didn’t change at all - it still receives name="Alice" - but it re-renders anyway.
This is why I need to be careful about component composition. If I have a heavy component that doesn’t need to update when its parent does, I should consider optimization.
3. Props Changes
If a component receives new props, it re-renders. This typically happens when the parent passes different values.
function UserCard({ user }) { console.log('UserCard re-rendered'); return ( <div> <p>Name: {user.name}</p> <p>Email: {user.email}</p> </div> );}
function App() { const [userId, setUserId] = useState(1);
// This creates a new object every render const user = { id: userId, name: `User ${userId}`, email: `user${userId}@example.com` };
return ( <div> <UserCard user={user} /> <button onClick={() => setUserId(userId + 1)}> Next User </button> </div> );}Every time I click “Next User”, the App component re-renders, creates a new user object, and passes it to UserCard. Since user is a new object reference each time, UserCard re-renders.
Common mistake I made: I used to think that if the object’s contents were the same, React wouldn’t re-render. But React uses referential equality for objects - a new object reference means new props, even if the values inside are identical.
4. Context Changes
When a context value changes, all components consuming that context re-render.
import { createContext, useContext, useState } from 'react';
const ThemeContext = createContext('light');
function ThemedButton() { const theme = useContext(ThemeContext); console.log('ThemedButton re-rendered'); return <button>Current theme: {theme}</button>;}
function App() { const [theme, setTheme] = useState('light');
return ( <ThemeContext.Provider value={theme}> <div> <ThemedButton /> <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}> Toggle Theme </button> </div> </ThemeContext.Provider> );}When I toggle the theme, ThemedButton re-renders because it consumes the context that changed.
Key insight: Even if I have multiple components consuming the same context, they’ll all re-render when the context value changes, regardless of which part of the context they actually use.
Common Mistake: Over-Optimizing
When I first learned about re-renders, I went overboard with optimization. I wrapped everything in React.memo, used useCallback everywhere, and added useMemo to every variable.
Here’s what I learned the hard way:
// DON'T do this unless you have a real performance problemfunction UserList({ users }) { // Unnecessary memoization const sortedUsers = useMemo(() => { return [...users].sort((a, b) => a.name.localeCompare(b.name)); }, [users]);
return ( <div> {sortedUsers.map(user => ( // Unnecessary memoization for simple components <MemoizedUserCard key={user.id} user={user} /> ))} </div> );}
const MemoizedUserCard = React.memo(function UserCard({ user }) { return <div>{user.name}</div>;});I was adding complexity without measuring if it helped. React is already fast for most use cases. I should only optimize when I’ve identified an actual performance bottleneck using React DevTools Profiler.
When to Actually Optimize
I optimize only when:
- Profiling shows a real problem - I use React DevTools Profiler to measure
- Heavy computations - I use
useMemofor expensive calculations - Frequent parent re-renders - I use
React.memofor components that receive stable props - Passing callbacks to optimized children - I use
useCallbackto maintain referential equality
function ExpensiveList({ items, filter }) { // Good: Filtering is expensive, so memoize it const filteredItems = useMemo(() => { console.log('Filtering items...'); return items.filter(item => item.category === filter); }, [items, filter]);
return ( <div> {filteredItems.map(item => ( <Item key={item.id} {...item} /> ))} </div> );}Summary
React components re-render for four reasons: state changes, parent re-renders, props changes, and context changes. Understanding these triggers helped me debug performance issues and write more predictable code.
The key insight I want to remember: don’t optimize prematurely. React’s default rendering behavior is fast enough for most cases. Measure first, optimize second.
Final Words + More Resources
My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me
Here are also the most important links from this article along with some further resources that will help you in this scope:
Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!
Comments