Performance Optimization with useMemo and useCallback
React components re-render whenever their state or props update. During a re-render, all functions inside the component are redeclared and complex calculations are re-evaluated. We can use useMemo and useCallback to cache these values and prevent unnecessary CPU overhead.
1. Caching Complex Calculations with useMemo
The useMemo hook caches (memoizes) the output of an expensive computation. It recalculates the value only when its dependencies change:
import { useState, useMemo } from "react";
function ProductList() {
const [query, setQuery] = useState("");
const [items, setItems] = useState([]);
// Complex filtering calculation
const filteredItems = useMemo(() => {
console.log("Running heavy filter operation...");
return items.filter(item => item.name.toLowerCase().includes(query.toLowerCase()));
}, [items, query]); // Recomputes only if items list or search query updates
return (
<div>
<input value={query} onChange={e => setQuery(e.target.value)} />
{/* Render filteredItems here */}
</div>
);
}2. Preventing Function Redeclaration with useCallback
In JavaScript, functions are objects, and comparing two objects with the same contents returns false if they point to different memory addresses:
// JavaScript equality check behavior
const fn1 = () => 1;
const fn2 = () => 1;
console.log(fn1 === fn2); // Outputs: falseEvery time a React component re-renders, all declared functions inside it are recreated at new memory locations. If you pass these functions as props to optimized child components, the children will re-render anyway because their props have changed.
The useCallback hook caches the function definition itself:
import { useState, useCallback } from "react";
import ChildComponent from "./ChildComponent";
function Parent() {
const [count, setCount] = useState(0);
// Caches the function address across parent renders
const handleClick = useCallback(() => {
console.log("Button clicked");
}, []); // Empty dependencies ensures function address remains identical
return (
<div>
<p>Parent renders: {count}</p>
<button onClick={() => setCount(count + 1)}>Re-render Parent</button>
<ChildComponent onClick={handleClick} />
</div>
);
}3. When NOT to Optimize
Do not wrap every function or calculation in useCallback or useMemo. Over-optimization adds complexity and memory overhead.
- Don't use it for simple operations (like basic string manipulations or sorting short arrays). The setup cost of the hook can be higher than the calculation itself.
- Use it when passing callbacks to optimized children (wrapped in
React.memo), or when the calculation involves processing large datasets.