Memoizing Functions
Memoizing Functions
useCallback returns a memoized function that only changes when dependencies change.
Basic Usage
import { useCallback } from "react";
function Counter() {
const [count, setCount] = useState(0);
// Without useCallback: new function every render
const handleClick = () => {
setCount(count + 1);
};
// With useCallback: same function reference
const handleClick = useCallback(() => {
setCount((prev) => prev + 1);
}, []); // No dependencies - function never changes
return (
<div>
<p>Count: {count}</p>
<button onClick={handleClick}>Increment</button>
</div>
);
}
With Dependencies
function TodoList({ onAdd }) {
const [input, setInput] = useState("");
const handleSubmit = useCallback(
(e) => {
e.preventDefault();
onAdd(input);
setInput("");
},
[input, onAdd] // Re-create when input or onAdd changes
);
return (
<form onSubmit={handleSubmit}>
<input value={input} onChange={(e) => setInput(e.target.value)} />
<button type="submit">Add</button>
</form>
);
}
Passing to Child Components
function Parent() {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
setCount((prev) => prev + 1);
}, []);
// Child won't re-render if it uses React.memo
return <Child onClick={handleClick} />;
}
const Child = React.memo(({ onClick }) => {
console.log("Child rendered");
return <button onClick={onClick}>Click me</button>;
});
useCallback vs useMemo
useCallback vs useMemo
Key Difference
- useMemo: Returns a memoized value
- useCallback: Returns a memoized function
Equivalent Behavior
// These are equivalent:
const memoizedFn = useCallback(() => doSomething(a, b), [a, b]);
const memoizedFn = useMemo(() => () => doSomething(a, b), [a, b]);
When to Use Each
// useMemo for values
const sortedItems = useMemo(
() => [...items].sort((a, b) => a.name.localeCompare(b.name)),
[items]
);
// useCallback for functions
const handleSort = useCallback(() => {
setSortBy((prev) => (prev === "asc" ? "desc" : "asc"));
}, []);
Reference Equality
// Without: new function reference every render
const handleDelete = (id) => {
setItems((prev) => prev.filter((item) => item.id !== id));
};
// With: same function reference
const handleDelete = useCallback(
(id) => {
setItems((prev) => prev.filter((item) => item.id !== id));
},
[]
);
Practical Example
function DataGrid({ data, onRowClick }) {
// Memoize expensive computation
const sortedData = useMemo(
() => [...data].sort((a, b) => a.name.localeCompare(b.name)),
[data]
);
// Memoize callback to prevent unnecessary re-renders
const handleClick = useCallback(
(rowId) => {
onRowClick(rowId);
},
[onRowClick]
);
return (
<div>
{sortedData.map((row) => (
<Row key={row.id} data={row} onClick={handleClick} />
))}
</div>
);
}
When to Use
When to Use
Good Use Cases
- Passing callbacks to memoized children
const Child = React.memo(({ onClick }) => ...);
const handleClick = useCallback(() => ..., []);
<Child onClick={handleClick} /> // Won't cause re-render
- Event handlers with dependencies
const handleSubmit = useCallback(
(e) => {
e.preventDefault();
submitForm(formData);
},
[formData]
);
- Functions passed to useEffect
const fetchData = useCallback(async () => {
const response = await fetch(url);
return response.json();
}, [url]);
useEffect(() => {
fetchData().then(setData);
}, [fetchData]);
When NOT to Use
- Inline handlers (not passed as props)
- Functions not used as dependencies
- Simple functions (overhead > benefit)
Cost vs Benefit
// Don't useCallback for this
const handleClick = useCallback(() => {
console.log("clicked");
}, []);
// Just use: onClick={() => console.log("clicked")}
// Do useCallback for this
const handleSort = useCallback(
(key) => {
setSortKey(key);
setSortOrder((prev) => (prev === "asc" ? "desc" : "asc"));
},
[]
);
// Pass to memoized child
<SortableList onSort={handleSort} />
Common Mistakes
// Bad: Creating new function every render
const handleClick = () => doSomething(id);
// Bad: Missing dependencies
const handleSubmit = useCallback(() => {
submit(formData); // formData is missing!
}, []);
// Good: Include all dependencies
const handleSubmit = useCallback(() => {
submit(formData);
}, [formData]);
Practice Problems
Create a reusable React component implementing useCallback. Include proper state management and accessibility.
Solution
// Production-ready component with:
// - Proper TypeScript types
// - Accessibility (ARIA)
// - Error boundaries
// - Loading states
// - Memoization where neededWrite unit and integration tests for useCallback using React Testing Library.
Solution
// Test coverage:
// 1. Rendering tests
// 2. Interaction tests
// 3. Edge case tests
// 4. Accessibility testsOptimize useCallback for performance. Consider memoization, code splitting, and bundle size.
Solution
// Optimization techniques:
// 1. React.memo / useMemo / useCallback
// 2. Code splitting with lazy()
// 3. Virtual scrolling for lists
// 4. Image lazy loading
// 5. Bundle analysisQuiz
1. What does useCallback return?
2. What is the difference between useCallback and useMemo?
3. When should you use useCallback?
4. What is referential equality?
Flashcards
Question
What does useCallback do?
Click to reveal answer
Answer
Returns a memoized function that only changes when dependencies change
Question
useCallback vs useMemo?
Click to reveal answer
Answer
useCallback returns a function, useMemo returns a value
Question
When to use useCallback?
Click to reveal answer
Answer
When passing callbacks to memoized children
Question
What is referential equality?
Click to reveal answer
Answer
Two references pointing to the same object in memory
Question
What is useCallback?
Click to reveal answer
Answer
useCallback is a key concept in frontend development.
Revision Notes
Key Takeaways
- 1.useCallback memoizes functions
- 2.Returns same function reference when dependencies unchanged
- 3.useful with React.memo for child components
- 4.Don't use for inline handlers
- 5.Include all dependencies in the array
Interview Tips
- •Explain useCallback vs useMemo
- •Show how it works with React.memo
- •Discuss when it's beneficial vs unnecessary
Cheat Sheet
Cheat Sheet
Basic Usage
const handleClick = useCallback(() => {
doSomething(a, b);
}, [a, b]);
With React.memo
const Child = React.memo(({ onClick }) => ...);
const handleClick = useCallback(() => ..., []);
<Child onClick={handleClick} />
useCallback vs useMemo
// useCallback for functions
const fn = useCallback(() => doStuff(), [dep]);
// useMemo for values
const value = useMemo(() => compute(dep), [dep]);
Common Pattern
const handleDelete = useCallback((id) => {
setItems(prev => prev.filter(i => i.id !== id));
}, []);