Avoiding Re-renders in React
Avoiding unnecessary re-renders in React is essential for optimizing application performance. React's reconciliation process identifies changes in the component tree and updates only the necessary parts. However, improper state management, props passing, or component structure can lead to redundant re-renders.
Techniques to Avoid Unnecessary Re-renders
-
Use
React.memoReact.memoprevents re-renders of functional components if their props haven’t changed.
import React from "react";
const Child = React.memo(({ name }) => {
console.log("Child rendered");
return <div>Hello, {name}</div>;
});
function Parent() {
const [count, setCount] = React.useState(0);
return (
<div>
<button onClick={() => setCount(count + 1)}>Increment</button>
<Child name="React" />
</div>
);
}
export default Parent;
- Without
React.memo,Childwould re-render every time the parent renders, even ifnameremains unchanged. -
React.memoensuresChildrenders only when itsnameprop changes.
-
Use
useCallbackfor Functions React re-creates functions on every render, which may trigger re-renders if passed as props. UseuseCallbackto memoize functions.
import React, { useState, useCallback } from "react";
const Child = React.memo(({ onClick }) => {
console.log("Child rendered");
return <button onClick={onClick}>Click Me</button>;
});
function Parent() {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
console.log("Button clicked");
}, []);
return (
<div>
<button onClick={() => setCount(count + 1)}>Increment</button>
<Child onClick={handleClick} />
</div>
);
}
export default Parent;
- Without
useCallback,handleClickwould be recreated on every render, causingChildto re-render. -
useCallbackmemoizeshandleClick, preventing unnecessary updates.
-
Use
useMemofor Expensive Calculations UseuseMemoto memoize computed values and avoid re-calculations on every render.
import React, { useState, useMemo } from "react";
function Parent() {
const [count, setCount] = useState(0);
const [other, setOther] = useState(0);
const expensiveCalculation = useMemo(() => {
console.log("Expensive calculation");
return count * 2;
}, [count]);
return (
<div>
<div>Result: {expensiveCalculation}</div>
<button onClick={() => setCount(count + 1)}>Increment Count</button>
<button onClick={() => setOther(other + 1)}>Increment Other</button>
</div>
);
}
export default Parent;
-
useMemoensures the expensive calculation is re-computed only whencountchanges.
- Avoid Inline Functions and Objects as Props Inline functions and objects are recreated on every render and can trigger re-renders.
Problematic Example:
const Child = React.memo(({ config }) => {
console.log("Child rendered");
return <div>Config: {JSON.stringify(config)}</div>;
});
function Parent() {
const config = { color: "blue" }; // Re-created on every render
return <Child config={config} />;
}
Solution: Use useMemo for Objects:
function Parent() {
const config = React.useMemo(() => ({ color: "blue" }), []);
return <Child config={config} />;
}
- Split Components into Smaller Pieces Break large components into smaller, reusable pieces. This limits the scope of updates.
const Counter = React.memo(({ count }) => {
console.log("Counter rendered");
return <div>Count: {count}</div>;
});
function App() {
const [count, setCount] = useState(0);
const [name, setName] = useState("");
return (
<div>
<Counter count={count} />
<input
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Enter name"
/>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
- Only
Counterwill re-render whencountchanges, not the input field.
-
Avoid Re-rendering with Context API
Context updates trigger re-renders for all consuming components. Use selective context or libraries like
zustandorReact Query.
Using Selector:
const CounterContext = React.createContext();
function CounterProvider({ children }) {
const [count, setCount] = useState(0);
return (
<CounterContext.Provider value={{ count, setCount }}>
{children}
</CounterContext.Provider>
);
}
function DisplayCount() {
const { count } = React.useContext(CounterContext);
console.log("DisplayCount rendered");
return <div>Count: {count}</div>;
}
function IncrementButton() {
const { setCount } = React.useContext(CounterContext);
console.log("IncrementButton rendered");
return <button onClick={() => setCount((c) => c + 1)}>Increment</button>;
}
function App() {
return (
<CounterProvider>
<DisplayCount />
<IncrementButton />
</CounterProvider>
);
}
- Here,
DisplayCountandIncrementButtonrender independently.
- Avoid Updating Parent State from Child Updating parent state from a child can force the parent and all its children to re-render. Pass handlers sparingly.
- Optimize Lists with Keys React needs unique keys for list items to avoid unnecessary re-renders.
const List = ({ items }) => {
return (
<ul>
{items.map((item) => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
};
Ensure key is unique and stable.
Tools to Detect Re-renders
- React Developer Tools: Inspect which components re-rendered.
-
why-did-you-renderLibrary: Logs unnecessary re-renders.
Conclusion
Avoiding unnecessary re-renders improves application performance, reduces rendering overhead, and enhances user experience. By understanding React’s rendering behavior and using tools like React.memo, useMemo, and useCallback, you can optimize your components effectively.
Top comments (0)