useCallback
is a React Hook that lets you cache a function definition between re-renders.
How to use useCallback?
useCallback(fn, dependencies)
import { useCallback } from 'react';
export default function ProductPage({ productId, referrer, theme }) {
const handleSubmit = useCallback((orderDetails) => {
post('/product/' + productId + '/buy', {
referrer,
orderDetails,
});
}, [productId, referrer]);
Parameters
fn
: The function value that you want to cache. It can take any arguments and return any values. React will return (not call!) your function back to you during the initial render. On next renders, React will give you the same function again if thedependencies
have not changed since the last render. Otherwise, it will give you the function that you have passed during the current render, and store it in case it can be reused later.dependencies
: The list of all reactive values referenced inside of thefn
code. Reactive values include props, state, and all the variables and functions declared directly inside your component body.
Returns
On the initial render, useCallback
returns the fn
function you have passed.
During subsequent renders, it will either return an already stored fn
function from the last render (if the dependencies haven’t changed), or return the fn
function you have passed during this render.
When to use useCallback?
// index.tsx - before
import { useState } from 'react';
// shuffle func just shuffle the array
import { shuffle } from '@/utils';
import Search from './Search';
const allUsers = [
'john',
'alex',
'george',
'simon',
'james',
];
interface DemoProps {}
export default function Demo({}: DemoProps) {
const [users, setUsers] = useState(allUsers);
const handleSearch = (text: string) => {
const filteredUsers = allUsers.filter((user) =>
user.includes(text),
);
setUsers(filteredUsers);
};
return (
<div className='tutorial'>
<div className='align-center mb-2 flex'>
<button onClick={() => setUsers(shuffle(allUsers))}>
Shuffle
</button>
<Search onChange={handleSearch} />
</div>
<ul>
{users.map((user) => (
<li key={user}>{user}</li>
))}
</ul>
</div>
);
}
// Search.tsx
// memo(): skip re-rendering of components when the props are unchanged
import { memo } from 'react';
interface SearchProps {
onChange: (text: string) => void;
}
function Search({ onChange }: SearchProps) {
return (
<input
type='text'
placeholder='Search users...'
onChange={(e) => onChange(e.target.value)}
/>
);
}
export default memo(Search);
In this case, everytime you click the Shuffle button
, React re-renders Search
component. Because in React functions are going to be considered different on every render, everytime you click Shuffle, React will consider handleSearch
as a different function so React will re-render Search
component since it receives handleSearch
.
Since the handleSearch
will continue to be the same function, there's no need to re-render Search
component on every render. So we can just reuse the previous function in the next render.
// index.tsx - after
import { useCallback, useState } from 'react'; // useCallback added
import { shuffle } from '@/utils';
import Search from './Search';
const allUsers = [
'john',
'alex',
'george',
'simon',
'james',
];
interface DemoProps {}
export default function Demo({}: DemoProps) {
const [users, setUsers] = useState(allUsers);
// More efficient!
const handleSearch = useCallback(
(text: string) => {
const filteredUsers = allUsers.filter((user) =>
user.includes(text),
);
setUsers(filteredUsers);
},
[],
);
return (
<div className='tutorial'>
<div className='align-center mb-2 flex'>
<button onClick={() => setUsers(shuffle(allUsers))}>
Shuffle
</button>
<Search onChange={handleSearch} />
</div>
<ul>
{users.map((user) => (
<li key={user}>{user}</li>
))}
</ul>
</div>
);
}
Using useCallback can reduce unnecessary renders and allows application to operate more efficiently.
🚨 useCallback is going to memorize/freeze everything in the function at a certain point. So use dependency array to control when the function should be different. 🚨
Reference