Why Your React App Re-renders Too Much (And How to Fix It)
Comments
Sign in to join the conversation
Sign in to join the conversation
React is famous for being fast, largely due to its Virtual DOM. However, "fast" doesn't mean "magic." One of the most common performance bottlenecks in React applications is unnecessary re-rendering.
When a component re-renders, React executes the function again, recalculates the Virtual DOM, and compares it to the previous version. If this happens too often or on heavy components, your UI can become laggy and unresponsive.
Here is a guide to understanding why this happens and practical strategies to fix it.
First, it is vital to understand when React renders. By default, a component re-renders if:
useState or useReducer.The most common misconception is that a child component only re-renders if its props change. This is false. Unless you explicitly tell React otherwise, a child always re-renders when the parent does.
Imagine you have a Parent component with a simple counter and a heavy Child component.
const Parent = () => {
const [count, setCount] = useState(0);
return (
<div>
<button onClick={() => setCount(c => c + 1)}>Count: {count}</button>
{/* The Child re-renders every time 'count' changes, even though it doesn't use 'count'! */}
<HeavyChild />
</div>
);
};
Every time you click the button, Parent re-renders. Consequently, HeavyChild also re-renders, wasting resources.
React.memoYou can wrap the child component in React.memo. This tells React: "Only re-render this component if its props have actually changed."
const HeavyChild = React.memo(() => {
console.log("Child rendered");
return <div className="heavy">I am a heavy component</div>;
});
Now, clicking the button in Parent updates the state, but HeavyChild stays dormant because it received no new props.
You successfully added React.memo, but your component still keeps re-rendering. Why? This usually happens when you pass objects or functions as props.
In JavaScript, {} !== {} and function() {} !== function() {}. Every time a component renders, all functions and objects defined inside it are recreated with new references in memory.
const Parent = () => {
const [darkTheme, setDarkTheme] = useState(false);
// This function is RE-CREATED on every render
const handleClick = () => {
console.log("Clicked");
};
return (
<div className={darkTheme ? 'dark' : 'light'}>
<button onClickFrom React's perspective, the onClick prop changed, so React.memo allows the re-render.
useCallback and useMemoTo preserve the "reference" of a function or object across renders, use hooks.
useCallback: Memoizes a function definition.useMemo: Memoizes a calculated value (object, array, or heavy calculation).const Parent = () => {
const [darkTheme, setDarkTheme] = useState(false);
// React keeps the same function reference across renders
const handleClick = useCallback(() => {
console.log("Clicked");
}, []); // Dependency array is empty, so it never changes
return (
<div className={darkTheme ? 'dark' : 'light'}>React Context is great for global state, but it has a major performance "gotcha." If you store a complex object in a Context Provider, every single consumer of that context will re-render whenever any part of that object changes.
If your Context value looks like this: {{ user, theme, settings }}, and you update theme, components that only care about user will typically still force a re-render check.
AppContext, use UserContext and ThemeContext.const MyProvider = ({ children }) => {
const [user, setUser] = useState(null);
// Without useMemo, this object is recreated every render, forcing consumers to update
const value = useMemo(() => ({ user, setUser }), [user]);
return <UserContext.Provider value={value}>{children}</UserContext.Provider>;
Don't guess—measure.