Background
useCallback and useMemo are two performance optimization hooks provided by React. They look similar but have fundamental differences in purpose and return values.
Core Difference
Syntax Comparison
jsx// useCallback: Returns the function itself const memoizedCallback = useCallback(() => { doSomething(a, b); }, [a, b]); // useMemo: Returns the result of function execution const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
Essential Differences
| Feature | useCallback | useMemo |
|---|---|---|
| Return Value | Function reference | Any value (computed result) |
| Purpose | Cache function | Cache computed result |
| Similar to | Function memoization | Value memoization |
| Use Case | Avoid function recreation | Avoid repeated computation |
useCallback Details
Basic Usage
jsxconst memoizedCallback = useCallback( () => { doSomething(a, b); }, [a, b] );
Use Cases
1. Passing to Child Components to Avoid Unnecessary Renders
jsxfunction Parent({ items }) { // ❌ Creates new function every render, causes Child to re-render const handleClick = () => { console.log("clicked"); }; // ✅ Use useCallback to cache function const handleClick = useCallback(() => { console.log("clicked"); }, []); return <Child onClick={handleClick} items={items} />; } // Use with React.memo const Child = React.memo(({ onClick, items }) => { console.log("Child render"); return <button onClick={onClick}>Click</button>; });
2. As useEffect Dependency
jsxfunction UserProfile({ userId }) { // ❌ fetchUser is new reference every time, useEffect runs every time const fetchUser = () => { api.getUser(userId); }; // ✅ Use useCallback to stabilize function reference const fetchUser = useCallback(() => { api.getUser(userId); }, [userId]); useEffect(() => { fetchUser(); }, [fetchUser]); }
useMemo Details
Basic Usage
jsxconst memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
Use Cases
1. Avoid Repeated Computation
jsxfunction ProductList({ products, filter }) { // ❌ Re-filters every render const filteredProducts = products.filter(p => p.name.includes(filter) ); // ✅ Only recomputes when products or filter changes const filteredProducts = useMemo(() => products.filter(p => p.name.includes(filter)), [products, filter] ); return <ul>{filteredProducts.map(p => <li key={p.id}>{p.name}</li>)}</ul>; }
2. Complex Computation Optimization
jsxfunction DataTable({ data }) { // ✅ Large data sorting computed once const sortedData = useMemo(() => { console.log("Sorting..."); return [...data].sort((a, b) => a.score - b.score); }, [data]); // ✅ Complex data transformation const chartData = useMemo(() => { return data.reduce((acc, item) => { // Complex aggregation logic return acc; }, {}); }, [data]); return <Chart data={chartData} />; }
3. Reference Stability
jsxfunction Parent({ items }) { // ❌ Creates new object every render, causes child re-render const style = { color: "red" }; // ✅ Keep object reference stable const style = useMemo(() => ({ color: "red" }), []); return <Child style={style} />; }
Best Practices
- Measure first, optimize later: Use React DevTools Profiler to find bottlenecks
- Simple computations do not need caching: Caching has overhead
- Use with React.memo: Ensure child components implement shouldComponentUpdate
- Declare dependencies correctly: Follow ESLint hints