Skip to content

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:

  1. Their state changes (via useState or useReducer)
  2. Their parent component re-renders
  3. Their props change
  4. 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.

StateTrigger.jsx
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.

ParentTrigger.jsx
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.

PropsTrigger.jsx
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.

ContextTrigger.jsx
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:

OverOptimized.jsx
// DON'T do this unless you have a real performance problem
function 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:

  1. Profiling shows a real problem - I use React DevTools Profiler to measure
  2. Heavy computations - I use useMemo for expensive calculations
  3. Frequent parent re-renders - I use React.memo for components that receive stable props
  4. Passing callbacks to optimized children - I use useCallback to maintain referential equality
AppropriateOptimization.jsx
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