React Suspense comes handy when you want to show an indication to user that something is loading in your app. Loader is the simplest example for a Suspense component. Let’s deep dive into the details of Suspense.
I hope you know what is useMemo
and understood that it caches the values between re-renders. useCallback
is similar to this and it caches a function instead of values. Let’s dive into the details of useCallback
.
What is useCallback?
useCallback
is a React Hook that helps you memoize a function definition, ensuring it remains the same across re-renders.
Although useCallback
shares a similar syntax with useMemo
, there’s an important distinction between the two. useMemo
executes a function and returns its result whenever its dependencies change. On the other hand, useCallback
doesn’t execute the function—instead, it returns a memoized version of the function itself.
This means React will maintain the same function reference as long as the dependencies don’t change, preserving referential equality. It’s worth noting that React doesn’t automatically call the function—it’s entirely up to you to decide when and how to use it.
Syntax:
const cachedFn = useCallback(function, dependencies)
Parameters
Here’s an improved version of your text with a polished tone:
function
: This is the function you want to memoize. It can accept any arguments and return any value. During the initial render, React will simply return (not call) your function. On subsequent renders, React will return the same function reference as long as thedependencies
haven’t changed. If thedependencies
do change, React will use the latest version of your function from the current render and update its cache for future use.dependencies
: This is a list of all reactive values used within your function. Reactive values includeprops
,state
, and any variables or functions defined inside your component’s body. If you don’t provide adependencies
array, React will always return the cached function and won’t update it, even if the reactive values inside the function change. This can lead to stale data if not handled carefully.
Usage
Below are the use cases of useCallback
:
- Skipping re-rendering of components.
- Updating state from a memoized callback.
- Preventing an Effect from firing too often.
- Optimizing a custom Hook.
Example:
To understand this concept better, let’s explore an example where we prevent unnecessary re-renders of components.
In the code below, every time the button to increment the count is clicked, the DemoOutput
component also re-renders. By default, when a component re-renders, React re-renders all of its children recursively. While this behavior is acceptable for lightweight components, it can negatively impact the app’s performance if the re-rendering process takes significant time.
Example without useCallback
// Counter.jsx
import { useState } from 'react';
import DemoOutput from './DemoOutput';
const Counter = () => {
const [count, setCount] = useState(0);
const [show, setShow] = useState(false);
const showOutput = () => {
setShow(!show);
};
return (
<div className="red-box">
<h3>Without useCallback</h3>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment count</button>
<DemoOutput show={show} showOutput={showOutput} />
</div>
);
};
export default Counter;
// DemoOutput.jsx
import React from 'react';
const DemoOutput = (props) => {
console.log('DemoOutput rendered.');
return (
<p>
<button onClick={props.showOutput}>Show Demo</button>
<br />
{props.show && 'Open the console and click the increment button.'}
</p>
);
};
export default React.memo(DemoOutput);
In this implementation, every click on the Increment count button triggers a re-render of the DemoOutput
component. This happens because the showOutput
function is re-created on each render, causing React to treat it as a new reference and re-render the memoized DemoOutput
component. You can verify this by observing the console logs.
Optimizing performance with useCallback
We can improve the performance by using the useCallback
hook to memoize the showOutput
function. By wrapping the function with useCallback
, React ensures the function maintains the same reference across renders unless its dependencies change. This prevents the unnecessary re-rendering of the DemoOutput
component.
Check the console logs to see the difference in rendering behavior between the two examples.
Here’s the optimized code:
// Counter.jsx
import { useState, useCallback } from 'react';
import DemoOutput from './DemoOutput';
const Counter = () => {
const [count, setCount] = useState(0);
const [show, setShow] = useState(false);
const showOutput = useCallback(() => {
setShow(!show);
}, [show]);
return (
<div className="green-box">
<h3>With useCallback</h3>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment count</button>
<DemoOutput show={show} showOutput={showOutput} />
</div>
);
};
export default Counter;
How it works
- The
showOutput
function is now wrapped inuseCallback
withshow
as a dependency. - The function is re-created only when
show
changes, maintaining referential equality otherwise. - The
React.memo
-wrappedDemoOutput
component will no longer re-render unnecessarily because itsshowOutput
prop remains the same between renders.
Summary
useCallback
is a React Hook that allows you to memoize a function, ensuring its reference remains unchanged across re-renders. This can significantly enhance performance by preventing unnecessary re-renders of child components that depend on the function.
Example repository
Check out the full working examples in the GitHub Repository.
What are your thoughts on this post?
I’d love to hear from you! Click this link to email me—I reply to every message!
Also use the share button below if you liked this post. It makes me smile, when I see it.