Main factors causing React component re-renders:
setState
or the setter function from useState
useMemo
or useEffect
changeExample of re-render triggered by state change:
function Counter() {
const [count, setCount] = useState(0);
// When the button is clicked, the state changes,
// triggering a component re-render
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
</div>
);
}
React components re-render when the data they depend on changes. This is a fundamental principle of reactive programming in React, but understanding specific causes of re-renders is critical for creating optimized applications. 🔍
Any change to a component’s internal state triggers a re-render:
function ToggleButton() {
const [isOn, setIsOn] = useState(false);
console.log("Component rendering"); // Runs every time state changes
return (
<button onClick={() => setIsOn(!isOn)}>
{isOn ? 'Turn Off' : 'Turn On'}
</button>
);
}
Each time the button is clicked, isOn
changes, which triggers a new render.
When a component receives new props from its parent, it re-renders:
// Child component
function Message({ text }) {
console.log("Message rendering");
return <p>{text}</p>;
}
// Parent component
function Chat() {
const [message, setMessage] = useState("");
return (
<div>
<input
value={message}
onChange={(e) => setMessage(e.target.value)}
/>
<Message text={message} /> {/* Re-renders on every input change */}
</div>
);
}
When a parent component re-renders, all its child components re-render by default:
function Parent() {
const [count, setCount] = useState(0);
return (
<div>
<button onClick={() => setCount(count + 1)}>
Increment counter: {count}
</button>
<Child /> {/* Re-renders on every click even though props don't change */}
</div>
);
}
function Child() {
console.log("Child rendering");
return <div>Child component</div>;
}
Components that use React Context re-render when the context value changes:
const ThemeContext = React.createContext('light');
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={theme}>
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
Toggle theme
</button>
{children}
</ThemeContext.Provider>
);
}
function ThemedButton() {
const theme = useContext(ThemeContext);
console.log("ThemedButton rendering");
return <button className={theme}>Themed Button</button>;
}
When the theme changes, all components using ThemeContext
will re-render.
Changes to dependencies in hooks can trigger recalculations and re-renders:
function SearchResults({ query }) {
// Re-computed when query changes
const filteredData = useMemo(() => {
console.log("Filtering performed");
return expensiveFilter(query);
}, [query]);
return (
<ul>
{filteredData.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
}
Prevents re-rendering if props haven’t changed:
// Won't re-render if name hasn't changed
const Greeting = React.memo(function Greeting({ name }) {
console.log("Greeting rendering");
return <h1>Hello, {name}!</h1>;
});
Caches calculation results between renders:
function ProductList({ products, filter }) {
// Filtering only runs when products or filter change
const filteredProducts = useMemo(() => {
return products.filter(product => product.category === filter);
}, [products, filter]);
return (
<ul>
{filteredProducts.map(product => (
<li key={product.id}>{product.name}</li>
))}
</ul>
);
}
Prevents creating new functions on every render:
function Parent() {
const [count, setCount] = useState(0);
// Function isn't recreated when count changes
const handleClick = useCallback(() => {
console.log('Clicked');
}, []);
return (
<div>
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
<ExpensiveChild onClick={handleClick} />
</div>
);
}
const ExpensiveChild = React.memo(function({ onClick }) {
console.log("ExpensiveChild rendering");
return <button onClick={onClick}>Child button</button>;
});
// ❌ Bad: new style object on every render
function Button() {
return (
<button
style={{ color: 'blue', fontSize: '14px' }} // New object on each render
>
Click me
</button>
);
}
// ✅ Good: constant outside the component
const buttonStyle = { color: 'blue', fontSize: '14px' };
function Button() {
return <button style={buttonStyle}>Click me</button>;
}
// ❌ Bad: new function on every render
function Parent() {
return (
<Child onClick={() => console.log('Clicked')} /> // New function on each render
);
}
// ✅ Good: using useCallback
function Parent() {
const handleClick = useCallback(() => {
console.log('Clicked');
}, []);
return <Child onClick={handleClick} />;
}
// ❌ Bad: missing data dependency
function DataList({ data }) {
useEffect(() => {
console.log("Data changed:", data);
}, []); // Missing data dependency
// ... rest of code
}
// ✅ Good: correct dependencies
function DataList({ data }) {
useEffect(() => {
console.log("Data changed:", data);
}, [data]);
// ... rest of code
}
console.log
inside the component bodyfunction MyComponent(props) {
console.log("MyComponent rendering", { props });
// ... component code
}
✅ Main causes of React re-renders:
✅ How to optimize re-renders:
React.memo
— prevents renders when props don’t changeuseMemo
— caches computation resultsuseCallback
— prevents function recreationUnderstanding the reasons for re-renders and knowing how to optimize them is a key skill for developing performant React applications. Optimize consciously, based on real performance issues rather than prematurely. 🚀
Want more articles to prepare for interviews? Subscribe to EasyAdvice, bookmark the site, and improve yourself every day 💪