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.memo
React.memo
prevents 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
,Child
would re-render every time the parent renders, even ifname
remains unchanged. -
React.memo
ensuresChild
renders only when itsname
prop changes.
-
Use
useCallback
for Functions React re-creates functions on every render, which may trigger re-renders if passed as props. UseuseCallback
to 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
,handleClick
would be recreated on every render, causingChild
to re-render. -
useCallback
memoizeshandleClick
, preventing unnecessary updates.
-
Use
useMemo
for Expensive Calculations UseuseMemo
to 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;
-
useMemo
ensures the expensive calculation is re-computed only whencount
changes.
- 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
Counter
will re-render whencount
changes, 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
zustand
orReact 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,
DisplayCount
andIncrementButton
render 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-render
Library: 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)