The callback hook in React is intended to optimize the rendering process, and the memo hook is used to create a memoized version of any object structure.
If you define a function within a component, it will be created again during each render process. In most cases, this is unnecessary. You can use the useCallback function to make React reuse the function unless certain aspects of the function change that require the function to be created.
Memoizing functions is especially interesting if you have a lot of functions in your component tree and this becomes a noticeable performance problem during render operations. You shouldn’t try to optimize every callback function in your components using the callback hook as it often happens that the hoped-for effect fails to materialize and the source code becomes less readable. The listing below shows an example of using the callback hook:
import { useCallback } from 'react';
import Rating from './Rating';
function BooksListItem({ book, onRate }) {
const handleRate = useCallback(
(event) => {
const rating = event.target.closest(
'[data-value]')?.dataset.value;
if (rating) {
onRate(book.id, parseInt(rating, 10));
}
},
[book.id, onRate]
);
return (
<tr>
<td>{book.title}</td>
<td>{book.author ? book.author : 'Unknown'}</td>
<td>{book.isbn}</td>
<td onClick={handleRate}>
<Rating item={book} />
</td>
</tr>
);
}
The component is the BooksListItem component, which is responsible for displaying a table row with a data record. The component has the rating component as a child component, which is responsible for displaying the rating and the option to change it. The BooksListItem component has a handleRate function that receives a click event and extracts the rating value from it, which is located in the data-value property of the clicked-on HTML element. Usually, this function is regenerated with each render operation.
If you use the useCallback function, React regenerates the function only if the ID of the record or the onRate function changes. The function needs both to do its job correctly. For this reason, you should name the information in the dependency array. The return value of the useCallback function is the memoized original function, which you can use like an ordinary function in your application.
You can use the memo hook to create a memoized version of any object structure. The functionality is similar to that of useCallback:
const memoObject = useMemo(() => doSomething(obj), [obj]);
In the case the code below, React stores the return value of the doSomething function in the memoObject variable and regenerates it if the obj dependency changes.
function reducer(state, action) {…}
function middleware(dispatch) {…}
function BooksList() {
const [books, dispatch] = useReducer(reducer, []);
const middlewareDispatch = useMemo(
() => middleware(dispatch), [dispatch]
);
useEffect(() => {
middlewareDispatch({ type: 'FETCH' });
}, [middlewareDispatch]);
…
return (
<table>…</table>
);
}
The middleware function shown above, to which you pass the dispatch function, creates the middlewareDispatch function that you can use in your component to trigger actions. You only need to regenerate the middlewareDispatch function if the dispatch function changes. The middlewareDispatch variable then contains a memoized variant of the generated function. You can then use this function in the effect hook, where you can also name it as a dependency as the function doesn’t change between the individual render operations.
If you were to run the middleware function to create the middlewareDispatch function without useMemo, the result would be that React would recreate the function on every render and thus also rerun the effect hook to load the data.
Note: useCallback and useMemo are very similar. You can use the useMemo function to simulate the same behavior as useCallback. useCallback(func, deps) is the same as use- Memo(() => func, deps).
Editor’s note: This post has been adapted from a section of the book React: The Comprehensive Guide by Sebastian Springer. Sebastian is a JavaScript engineer at MaibornWolff. In addition to developing and designing both client-side and server-side JavaScript applications, he focuses on imparting knowledge. He inspires enthusiasm for professional development with JavaScript as a lecturer for JavaScript, a speaker at numerous conferences, and an author. Sebastian was previously a team leader at Mayflower GmbH, one of the premier web development agencies in Germany. He was responsible for project and team management, architecture, and customer care for companies such as Nintendo Europe and Siemens.
This post was originally published in 3/2025.
Comments