React's optimization hooks, useMemo
and useCallback
, are powerful tools for improving application performance. While they serve similar purposes—preventing unnecessary recalculations and re-renders—they're designed for different use cases.
Core Differences
useMemo:
- Memoizes return values
- Syntax:
const value = useMemo(() => computeValue(a, b), [a, b])
- Use for expensive calculations
useCallback:
- Memoizes functions
- Syntax:
const handler = useCallback(() => doSomething(a, b), [a, b])
- Use for function references passed to child components
Understanding useMemo
The useMemo
hook memoizes the result of a computation, preventing expensive calculations from running on every render.
import { useMemo } from "react";
function ExpensiveComponent({ data, filter }) {
// This expensive computation will only re-run when data or filter changes
const filteredData = useMemo(() => {
console.log("Filtering data...");
return data.filter((item) => item.name.includes(filter));
}, [data, filter]);
return (
<div>
<h2>Filtered Results: {filteredData.length}</h2>
{filteredData.map((item) => (
<div key={item.id}>{item.name}</div>
))}
</div>
);
}
When to Use useMemo:
- Expensive calculations: When computing a value requires significant CPU work
- Complex data transformations: When filtering, sorting, or mapping large arrays
- Preventing recreations of reference types: When you need to maintain referential equality for objects or arrays
Understanding useCallback
The useCallback
hook memoizes a function definition, preventing the function from being recreated on every render.
import { useCallback } from "react";
function ParentComponent({ id }) {
// This function reference remains the same between renders
// as long as id doesn't change
const handleClick = useCallback(() => {
console.log("Clicked item with id:", id);
// Perform some action with id
}, [id]);
return (
<div>
<h2>Parent Component</h2>
<ChildComponent onClick={handleClick} />
</div>
);
}
// Child component using React.memo to prevent unnecessary re-renders
const ChildComponent = React.memo(function ChildComponent({ onClick }) {
console.log("Child component rendered");
return <button onClick={onClick}>Click Me</button>;
});
When to Use useCallback:
- Event handlers passed to optimized child components: When the function is a prop for a memoized component
- Functions as dependencies: When the function is used in dependency arrays of other hooks
- Functions passed to custom hooks: To maintain referential equality between renders
Practical Use Case Comparison
Using useMemo
// Bad - recalculates on every render
function ProductList({ products, category }) {
const filteredProducts = products.filter(
(product) => product.category === category
);
return (
<ul>
{filteredProducts.map((product) => (
<li key={product.id}>{product.name}</li>
))}
</ul>
);
}
// Good - only recalculates when products or category changes
function ProductList({ products, category }) {
const filteredProducts = useMemo(
() => products.filter((product) => product.category === category),
[products, category]
);
return (
<ul>
{filteredProducts.map((product) => (
<li key={product.id}>{product.name}</li>
))}
</ul>
);
}
Using useCallback
// Bad - creates a new function on every render
function SearchComponent({ onSearch }) {
const handleSearchChange = (e) => {
onSearch(e.target.value);
};
return <input onChange={handleSearchChange} />;
}
// Good - function reference stays the same across renders
function SearchComponent({ onSearch }) {
const handleSearchChange = useCallback(
(e) => {
onSearch(e.target.value);
},
[onSearch]
);
return <input onChange={handleSearchChange} />;
}
Performance Considerations
Both hooks exist to optimize performance, but incorrect usage can actually harm performance:
- Premature optimization: Don't wrap everything in
useMemo
oruseCallback
- Hook overhead: The hooks themselves have a cost; only use when necessary
- Dependency arrays: Incorrect dependency arrays can lead to stale closures or unnecessary recalculations
Rules of Thumb
- Start without optimization: Add
useMemo
oruseCallback
only when you've identified a performance issue - Measure the impact: Use React DevTools Profiler to verify performance improvements
- Don't memoize simple calculations: For simple operations, the overhead of memoization may exceed the benefits
- Always use with React.memo:
useCallback
provides the most benefit when paired with memoized child components
When Neither is Needed
Don't use these hooks for:
- Simple calculations that are already fast
- Components that will re-render anyway due to other state changes
- Functions that don't get passed to child components or used in dependency arrays
Conclusion
While useMemo
and useCallback
may seem similar at first glance, they serve distinct purposes:
- Use
useMemo
when you need to avoid repeating expensive calculations - Use
useCallback
when you need to maintain function reference equality for child components
Understanding when and how to apply these hooks will help you build more performant React applications without sacrificing code readability or maintainability.
Remember: optimization should be purposeful, not habitual. Always identify actual performance bottlenecks before applying these techniques.