Optimizing rendering performance is essential for creating smooth and responsive user interfaces. One powerful technique for achieving this optimization is to use memoized callback functions with the useCallback
hook. By memoizing callback functions, React can efficiently manage updates and prevent unnecessary re-renders of components.
Memoization with the memo
function is another key aspect of optimizing rendering performance in React. The memo
function prevents unnecessary re-renders of a component by caching the result of the component’s render function and reusing it when the component’s props remain the same. Dive deeper into React optimization in the article “Mastering React: Optimizing Rendering Performance With Memoization“.
useCallback
hook in conjunction with the memo
function is crucial for optimizing performance and avoiding unnecessary re-renders. If you create a function (e.g. called onClick
) in the component, a new instance of the function will be created on each render of the component. It means that every time the component re-renders, even if its dependencies haven’t changed, a new onClick
function will be created.
When you wrap the onClick
function with the useCallback
hook, React will memoize the function that remains the same between renders unless any of its dependencies change. It means that if the dependencies of the useCallback
hook remain unchanged, the same instance of the onClick
function will be reused across renders.
Example without memoizing
Main.tsx
export default function Main() {
const [refresh, setRefresh] = useState<boolean>(false);
function onClick() {
console.log("Clicked");
}
return (
<div>
<main>
<h1>Main component</h1>
<button onClick={() => setRefresh(true)}>Set refresh true</button>
<button onClick={() => setRefresh(false)}>Set refresh false</button>
<Button onClick={onClick} />
</main>
</div>
);
}
Button.tsx
import { memo } from "react";
function Component({ onClick }: { onClick: () => void }) {
return <button onClick={onClick}>Click me!</button>;
}
const Button = memo(Component);
export default Button;
By memoizing the onClick
function with useCallback
, React optimizes performance by avoiding unnecessary re-creations of the function on each render. This is particularly useful when the component containing the onClick
function re-renders frequently, as it prevents the Button
component from re-rendering unnecessarily due to changes in the onClick
function reference.
Example with memoizing
Main.tsx
export default function Main() {
const [refresh, setRefresh] = useState<boolean>(false);
const onClick = useCallback(function onClick() {
console.log("Clicked");
}, []);
return (
<div>
<main>
<h1>Main component</h1>
<button onClick={() => setRefresh(true)}>Set refresh true</button>
<button onClick={() => setRefresh(false)}>Set refresh false</button>
<Button onClick={onClick} />
</main>
</div>
);
}
Using useCallback
hook for the onClick
function ensures that the function remains stable across renders, which in turn improves the efficiency of the Button
component wrapped in memo
by minimizing unnecessary re-renders.
If the onClick
function is not wrapped with useCallback
hook (it’s not memoized), a new instance of the function will be created on each render of the Main
component. Although the Button component is wrapped in the memo
function, it will still re-render if its props, such as onClick
, change. memo
only prevents re-renders if the props remain the same between renders, but if any prop changes, including onClick
, the Button
component will re-render.
Conclusion
To prevent unnecessary re-renders of the Button
component, you need to memoize all passed functions as props. By memoizing these functions, React ensures that the component only re-renders when its props change their references, rather than their content. This optimization can significantly improve the performance of the application, especially in scenarios where components contain many callback functions passed as props.
If the onClick
function is not memoized with the useCallback hook, the Button
component will be re-rendered each time the Main
component is re-rendered. This happens because every time the Main
component re-renders, a new instance of the onClick
function is created. Since the onClick
function is passed as a prop to the Button
component, even if the functionality of the function remains the same, the Button
component will re-render because its prop reference (onClick) has changed.