React useMemo useCallback hooks
You should use
useMemoanduseCallbackin React withTypeScriptprimarily for performance optimization by memoizing expensive calculations or function definitions, preventing unnecessary re-renders of child components.
💡 Key Concepts: Memoization
Memoization is a technique where the result of an expensive function call is cached, and the cached result is returned when the same inputs occur again.
useMemo: Memoizes a value.useCallback: Memoizes a function definition.
1. When and How to use useMemo
The useMemo hook is used to memoize a calculated value. It only re-calculates the value when one of its dependencies changes.
➡️ Syntax (TypeScript)
const memoizedValue = useMemo<TResult>(
() => {
// Expensive calculation goes here
return result;
},
[dependency1, dependency2] // Dependencies array
);🗓️ Real Example Situation: Expensive Calculation
Use useMemo when you have a complex, time-consuming calculation inside your component that shouldn't run on every single render, especially if only unrelated props/state have changed.
Scenario: Filtering a large list of items based on a search term.
import React, { useMemo, useState } from 'react';
// Define the type for an item
interface Item {
id: number;
name: string;
category: string;
}
const ItemList: React.FC<{ items: Item[]; searchTerm: string }> = ({ items, searchTerm }) => {
// Use useMemo to memoize the filtered list
const filteredItems = useMemo<Item[]>(() => {
console.log('Filtering items...'); // This log indicates when the calculation runs
if (!searchTerm) {
return items;
}
return items.filter(item =>
item.name.toLowerCase().includes(searchTerm.toLowerCase())
);
}, [items, searchTerm]); // Dependencies: only re-calculate when 'items' or 'searchTerm' changes
return (
<div>
<h3>Filtered Results ({filteredItems.length})</h3>
<ul>
{filteredItems.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</div>
);
};
// --- Parent Component for context ---
const App: React.FC = () => {
const [searchTerm, setSearchTerm] = useState('');
const [count, setCount] = useState(0); // State for unrelated re-renders
const data: Item[] = useMemo(() => ([ /* large array of data */ ]), []); // Mock data
return (
<div>
<input
type="text"
placeholder="Search..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
<button onClick={() => setCount(c => c + 1)}>
Rerender (Count: {count})
</button>
{/* ItemList's filtering is skipped when 'count' changes, but 'searchTerm' doesn't */}
<ItemList items={data} searchTerm={searchTerm} />
</div>
);
};2. When and How to use useCallback
The useCallback hook is used to memoize a function definition. It returns a memoized version of the function that only changes if one of its dependencies has changed.
Syntax (TypeScript)
const memoizedFunction = useCallback<TFunction>(
(arg1: TArg1, arg2: TArg2): TResult => {
// Function logic goes here
return result;
},
[dependency1, dependency2] // Dependencies array
);🗓️ Real Example Situation: Passing a Prop to a Memoized Child Component
Use useCallback when you pass a function as a prop to a child component that is wrapped in React.memo().
Why? In React, functions are recreated on every render of the parent component. When a new function is passed as a prop, the React.memo() child component sees a prop change (since oldFunction !== newFunction) and re-renders unnecessarily. useCallback prevents this.
Scenario: Preventing a memoized button component from re-rendering just because its onClick handler is recreated.
import React, { useCallback, useState } from 'react';
// 1. Child component wrapped in React.memo for optimization
interface ButtonProps {
onClick: (value: string) => void;
label: string;
}
// React.memo ensures this component only re-renders if its props change (shallow comparison)
const MemoizedButton = React.memo<ButtonProps>(({ onClick, label }) => {
console.log(`Rendering ${label} Button`);
return (
<button onClick={() => onClick(label)}>
{label}
</button>
);
});
// 2. Parent component
const ParentComponent: React.FC = () => {
const [count, setCount] = useState(0);
const [inputVal, setInputVal] = useState('');
// ❌ Without useCallback: handleButtonClick would be a new function on every render,
// forcing MemoizedButton to re-render even if 'count' hasn't changed.
// ✅ With useCallback: The function definition is memoized. It only changes
// when 'count' (its dependency) changes.
const handleButtonClick = useCallback((buttonLabel: string) => {
setCount(c => c + 1);
console.log(`Button ${buttonLabel} clicked. New count: ${count + 1}`);
}, [count]); // Dependency: The count state, since it's used inside setCount (though here c=>c+1 is safer)
return (
<div>
<input
value={inputVal}
onChange={(e) => setInputVal(e.target.value)}
placeholder="Typing causes re-render"
/>
<p>Current Count: {count}</p>
{/* This button will only re-render when 'count' changes, thanks to useMemoizedButton and useCallback */}
<MemoizedButton
label="Increment"
onClick={handleButtonClick}
/>
</div>
);
};🛑 When NOT to use them
Avoid using useMemo or useCallback in these situations:
- Trivial Calculations: If the computation is simple (e.g.,
a + b, a simple string concatenation, or filtering a small array), the overhead of the hook itself often outweighs the performance gain. - Every Function/Value: Don't wrap every function or value in your component. This adds complexity and memory overhead. Only use them for performance-critical scenarios (heavy computations or passing props to memoized child components).
- No Dependencies: If your function or value doesn't rely on state or props, you can often define it outside the component function entirely, which is the ultimate form of memoization.