Topic 47 of 54
Caching Functions
Overview
Functions defined inside a component are re-created as brand new functions in memory on every single render. Usually, this is fine. But if you pass that function as a prop to a child component wrapped in `React.memo`, the child will re-render anyway because it sees a 'new' function reference. `useCallback` is exactly like `useMemo`, but specifically for caching *function references*.
Syntax
If we didn't use `useCallback`, `handleAdd` would be a new function every time the user typed a letter, breaking the `memo` on `ExpensiveList`.
Stabilizing a Function Prop
jsx
import { useState, useCallback, memo } from 'react';
const ExpensiveList = memo(({ onAdd }) => {
console.log("List Rendered!");
return <button onClick={onAdd}>Add Item</button>;
});
function Parent() {
const [text, setText] = useState("");
const [items, setItems] = useState([]);
// 1. Wrap the function in useCallback
// 2. The function reference will remain identical across renders
const handleAdd = useCallback(() => {
setItems(prev => [...prev, "New Item"]);
}, []); // Empty array = function never changes
return (
<div>
{/* Typing in the input re-renders Parent, but won't re-render ExpensiveList! */}
<input value={text} onChange={e => setText(e.target.value)} />
<ExpensiveList onAdd={handleAdd} />
</div>
);
}Common Pitfalls
- Wrapping every function in `useCallback`. It actually *hurts* performance unless it is specifically being passed down to a `React.memo` component, or used inside a `useEffect` dependency array.
Interview Tips
- Remember: `useCallback(fn, deps)` is literally just syntactic sugar for `useMemo(() => fn, deps)`.
Real-World Example
Passing functions into `useEffect` dependency arrays. If you use a function inside an effect, the linter demands you put it in the array. If it's not wrapped in `useCallback`, it will trigger the effect infinitely.
example
jsx
const fetchUserData = useCallback(async () => {
return await api.get(`/users/${id}`);
}, [id]);
useEffect(() => {
fetchUserData();
}, [fetchUserData]); // Safe to add here because it's stabilized