Boosting React Performance: A Deep Dive into "useCallback" and "React.memo"
useCallback
and React.memo
Both are powerful tools in React to optimize rendering performance, especially in complex applications. When used effectively, they help avoid unnecessary re-renders, making your app more efficient.
1. useCallback
Hook
The useCallback
hook memoizes a function, preventing it from being recreated on every render. This is useful when passing functions down to child components as props, because without useCallback
, the child would re-render every time the parent component re-renders, even if the function itself hasn’t changed.
Syntax
const memoizedFunction = useCallback(() => {
// Your function logic here
}, [dependencies]);
Parameters:
Function to memoize: This is the function you want React to keep from recreating unless dependencies change.
Dependency array: Similar to
useEffect
, it specifies when the function should be updated. If any dependency changes, React recreates the function.
Example
Imagine a component with a button that updates a counter and a function that handles logging. We'll see how useCallback
optimizes it.
import React, { useState, useCallback } from 'react';
function Counter() {
const [count, setCount] = useState(0);
const increment = () => setCount(count + 1);
// Memoizing the logging function with useCallback
const logCount = useCallback(() => {
console.log(`Current count: ${count}`);
}, [count]); // Only re-creates the function if `count` changes
return (
<div>
<h2>Count: {count}</h2>
<button onClick={increment}>Increment</button>
<button onClick={logCount}>Log Count</button>
</div>
);
}
Explanation:
Without
useCallback
: Every timeCounter
re-renders (like whenincrement
is called),logCount
would be recreated, causing any child components that depend on it to re-render as well.With
useCallback
:logCount
only updates whencount
changes, ensuring that child components relying onlogCount
don’t re-render unnecessarily.
2. React.memo
React.memo
is a higher-order component that memoizes the component itself. It prevents the component from re-rendering unless its props change, which is ideal for functional components receiving the same props repeatedly.
Syntax
const MemoizedComponent = React.memo(Component);
Example:
Now, let’s add a child component that displays the count and only re-renders when the count itself changes.
import React, { useState, useCallback } from 'react';
// Memoized child component
const DisplayCount = React.memo(({ count }) => {
console.log('DisplayCount rendered');
return <h2>Count: {count}</h2>;
});
function Counter() {
const [count, setCount] = useState(0);
const increment = () => setCount(count + 1);
const logCount = useCallback(() => {
console.log(`Current count: ${count}`);
}, [count]);
return (
<div>
<DisplayCount count={count} />
<button onClick={increment}>Increment</button>
<button onClick={logCount}>Log Count</button>
</div>
);
}
Explanation:
Without
React.memo
: Every timeCounter
re-renders,DisplayCount
would re-render as well, even ifcount
hasn’t changed.With
React.memo
:DisplayCount
only re-renders if its prop (count
) changes, making it more efficient.
Key Takeaways and Best Practices
Use
useCallback
when passing functions down to child components to avoid function recreation and unnecessary re-renders.Use
React.memo
to wrap components that receive stable props, ensuring they only re-render when their props change.
Combining useCallback
with React.memo
for Optimal Performance
Let’s create an example where these two are combined effectively:
import React, { useState, useCallback } from 'react';
const MemoizedButton = React.memo(({ handleClick, label }) => {
console.log(`Rendering button - ${label}`);
return <button onClick={handleClick}>{label}</button>;
});
function Counter() {
const [count, setCount] = useState(0);
const [otherState, setOtherState] = useState(false);
const increment = useCallback(() => {
setCount(prevCount => prevCount + 1);
}, []);
const toggleOtherState = useCallback(() => {
setOtherState(prevState => !prevState);
}, []);
return (
<div>
<h2>Count: {count}</h2>
<MemoizedButton handleClick={increment} label="Increment Count" />
<MemoizedButton handleClick={toggleOtherState} label="Toggle State" />
<p>Other state: {otherState ? 'On' : 'Off'}</p>
</div>
);
}
Breakdown:
MemoizedButton
: A memoized component that receiveshandleClick
andlabel
as props. It only re-renders when these props change.increment
andtoggleOtherState
: Both functions are memoized usinguseCallback
, preventing unnecessary re-renders ofMemoizedButton
.
Benefits:
Reusability: The memoized component
MemoizedButton
is only updated when necessary, optimizing rendering time.Code Readability:
useCallback
andReact.memo
combined help make the intent of the code clearer by explicitly defining when functions and components should or shouldn’t re-render.
By combining useCallback
and React.memo
, you can control re-renders on a granular level, ensuring your app remains performant and readable. This approach is particularly useful for apps with complex component trees or when you need to pass callbacks deeply into the tree.
Subscribe to my newsletter
Read articles from Asawer directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by