useCallback HookThe useCallback hook is an advanced performance optimization Hook. It returns a memoized (cached) version of a callback function that only changes if one of its dependencies has changed.
To understand why this is useful, we must understand how React renders components and how JavaScript evaluates function equality.
In React, whenever a component re-renders, every function defined inside that component is recreated.
In JavaScript, two functions are never equal, even if they have the exact same code.
const func1 = () => console.log('Hi'); const func2 = () => console.log('Hi'); console.log(func1 === func2); // Returns FALSE
If a parent component passes a callback function as a prop to a child component, the child component will see that function as a brand new prop on every render, causing the child to unnecessarily re-render (even if you wrapped the child in React.memo).
useCallbackBy wrapping a function in useCallback, you tell React to save (memoize) that function definition between renders. React will only recreate the function if the dependencies in the dependency array change.
const memoizedCallback = useCallback( () => { doSomething(a, b); }, [a, b], // Dependency array );
Consider this scenario where typing in an input forces a heavy list component to re-render, making the app sluggish:
import React, { useState } from 'react'; import ExpensiveList from './ExpensiveList';function App() { const [text, setText] = useState(''); const [items, setItems] = useState(['Apple', 'Banana', 'Cherry']);
// This function is recreated every time we type in the input! const removeItem = (itemToRemove) => { setItems(items.filter(item => item !== itemToRemove)); };
return ( <div> <input value={text} onChange={(e) => setText(e.target.value)} /> {/* Because removeItem is re-created, ExpensiveList re-renders every keystroke! */} <ExpensiveList items={items} onRemove={removeItem} /> </div> ); }
useCallbackTo fix this, we wrap the removeItem function in useCallback and wrap the child component in React.memo (which prevents a component from re-rendering if its props haven't changed).
import React, { useState, useCallback } from 'react'; // Assume ExpensiveList is wrapped in React.memo() import ExpensiveList from './ExpensiveList';function App() { const [text, setText] = useState(''); const [items, setItems] = useState(['Apple', 'Banana', 'Cherry']);
// React caches this function. It only changes if 'items' changes. const removeItem = useCallback((itemToRemove) => { setItems(items.filter(item => item !== itemToRemove)); }, [items]); // Dependencies
return ( <div> <input value={text} onChange={(e) => setText(e.target.value)} /> {/* Typing in the input no longer causes ExpensiveList to re-render! */} <ExpensiveList items={items} onRemove={removeItem} /> </div> ); }
Now, when you type in the text input, the App component re-renders, but useCallback returns the exact same removeItem function as before. ExpensiveList sees that its props haven't changed, and skips rendering!
useCallback?You shouldn't wrap every function in useCallback. Caching functions requires memory and computation.
Only use useCallback when:
React.memo.useEffect or useMemo).For simple components, the cost of re-creating the function is lower than the cost of running the useCallback logic.
Reminder: If your callback uses state variables or props from the component scope, you must include them in the dependency array. Otherwise, the cached function will execute with stale, outdated data.
What is the primary purpose of the useCallback Hook?