React: What's the Difference Between useMemo and useCallback? When Should You Use Them?
Concise Answer
useMemo and useCallback are both React Hooks used for performance optimization, but they cache different things:
useMemocaches a computed result.useCallbackcaches a function reference.
More specifically, useMemo executes the calculation function you pass in and caches its return value. In normal cases, as long as the dependencies have not changed, React reuses the previously cached result. When a dependency changes, React runs the calculation function again.
For example:
const activeItems = useMemo(
() => items.filter(item => item.active),
[items]
);
JavaScriptThis avoids repeating the same filtering calculation when the dependencies have not changed.
useCallback, on the other hand, caches a function reference. In normal cases, as long as the dependencies have not changed, React returns the previously cached function reference.
For example:
const handleSelect = useCallback((id) => {
setSelectedId(id);
}, []);
JavaScriptIt is commonly used in scenarios such as:
- Passing a function as a prop to a child component wrapped with
React.memo. - Using a function as a dependency of another Hook.
- Working with third-party components or custom Hooks that depend on stable function references.
In short:
| Hook | What It Caches | Common Uses |
|---|---|---|
useMemo | Computed result | Avoiding expensive recalculations, stabilizing object or array references |
useCallback | Function reference | Stabilizing callback references, working with React.memo, serving as a dependency of other Hooks |
Keep in mind that useMemo and useCallback also introduce costs such as dependency comparison and cache maintenance. You should not mechanically memoize every calculation or function.
Prefer them in clear cases where computation is expensive, a stable reference is required, they work together with React.memo, or a function needs to be used as a dependency of another Hook. For performance-sensitive code, it is best to verify the actual benefit with tools such as the React DevTools Profiler.
Extended Analysis
Detailed Explanation
The key to understanding these two Hooks is recognizing that they solve two different problems:
useMemomainly addresses repeated computation.useCallbackmainly addresses unstable function references.
During subsequent renders of a component, React uses Object.is to compare each item in the dependency array with the corresponding dependency from the previous render.
If any dependency changes:
useMemoreruns the calculation function and caches the new result.useCallbackreturns the new function reference passed during the current render.
Therefore, a more accurate description is not that "React performs a shallow comparison of the dependency array," but rather:
React compares dependencies one by one using
Object.is.
For primitive values, such as:
1
'hello'
true
JavaScriptReact compares their values.
For objects, arrays, and functions, if they are recreated on every render, their references are usually different even when their contents are identical. React therefore treats those dependencies as changed.
For example:
const options = {
sort: 'price'
};
JavaScriptIf this code is written directly inside the component function, a new object is created on every render.
As a result:
useMemo(() => {
// ...
}, [options]);
JavaScriptthe options dependency will likely be considered changed on every render, causing useMemo to lose its intended caching benefit.
You can think of dependency changes like this:
Component re-renders
↓
React uses Object.is
to compare dependencies one by one
↙ ↘
No change Changed
↓ ↓
Reuse cache Recompute / return new reference
↓
Update cache
↘ ↙
Component finishes rendering
It is important to note that both useMemo and useCallback are performance optimization techniques, not semantic guarantees for application logic.
You should therefore never rely on them to guarantee correctness. Even if React discards the cache, the component should still behave correctly.
useMemo: Caching Computed Results
The main performance benefit of useMemo comes from avoiding repeated execution of expensive calculations.
For example:
const result = useMemo(() => {
return expensiveCalculation(data);
}, [data]);
JavaScriptWhen data has not changed, React will normally reuse the previous result instead of running expensiveCalculation again.
Whether useMemo is worthwhile depends on:
Cost of recomputation
VS
Cost of dependency comparison + cache maintenance
If the calculation is only:
const total = price * count;
JavaScriptor:
const names = users.map(user => user.name);
JavaScriptand the dataset is very small, useMemo will often provide little or no measurable benefit while making the code more complex.
However, useMemo can be useful for:
- Filtering large datasets.
- Sorting with multiple conditions.
- Deep recursive calculations.
- Complex data transformations.
- Preprocessing data for large charts.
In such cases, useMemo may provide real value.
useCallback: Caching Function References
In JavaScript, functions are also objects.
For example:
function Parent() {
const handleClick = () => {
console.log('clicked');
};
return <Child onClick={handleClick} />;
}
JavaScriptEvery time Parent re-renders:
const handleClick = () => {
console.log('clicked');
};
JavaScripta new function object is created.
That means:
previousHandleClick === currentHandleClick
JavaScriptwill usually evaluate to:
false
JavaScriptIf Child is wrapped with React.memo:
const Child = React.memo(function Child({ onClick }) {
return <button onClick={onClick}>Click</button>;
});
JavaScriptcreating a new handleClick on every parent render causes the onClick prop to be considered changed.
This does not mean that React.memo has "stopped working." Rather:
Because the function prop has a different reference,
React.memocannot skip this child render based solely on prop equality.
You can use:
const handleClick = useCallback(() => {
console.log('clicked');
}, []);
JavaScriptAs long as the dependencies do not change, React will normally keep the function reference stable.
Practical Applications
Scenario 1: Complex Product Filtering
In e-commerce applications, you often need to filter a large number of products using multiple criteria:
const filteredProducts = useMemo(() => {
return products
.filter(product => {
return (
product.price >= priceRange[0] &&
product.price <= priceRange[1] &&
selectedBrands.includes(product.brand) &&
product.rating >= minRating
);
})
.sort((a, b) => a.price - b.price);
}, [products, priceRange, selectedBrands, minRating]);
JavaScriptSuppose products contains several thousand items, and every filtering operation also includes brand checks, price-range checks, rating checks, and sorting. This type of computation may be worth memoizing with useMemo.
However, you need to pay special attention to priceRange:
const priceRange = [minPrice, maxPrice];
JavaScriptIf the parent component creates a new array on every render, then even when:
minPrice
maxPrice
JavaScripthave not changed at all, the priceRange reference may still be different, causing useMemo to run again.
You can instead split the dependency into primitive values:
const filteredProducts = useMemo(() => {
return products
.filter(product => {
return (
product.price >= minPrice &&
product.price <= maxPrice &&
selectedBrands.includes(product.brand) &&
product.rating >= minRating
);
})
.sort((a, b) => a.price - b.price);
}, [products, minPrice, maxPrice, selectedBrands, minRating]);
JavaScriptAlternatively, make sure that the priceRange reference itself is stable.
Scenario 2: Stabilizing Object References
Another common use case is passing a configuration object to a child component.
For example:
const chartConfig = useMemo(() => ({
type: 'line',
data: salesData,
options: {
responsive: true,
scales: {
y: {
beginAtZero: true
}
}
}
}), [salesData]);
JavaScriptIf this configuration object needs to be passed to a component that depends on reference stability:
<Chart config={chartConfig} />
JavaScriptusing useMemo can prevent the parent from creating a new configuration object on every render.
However, one important point remains:
useMemoonly makes sense when keeping the object reference stable actually avoids additional work.
If Chart re-renders every time anyway and creating the configuration object is extremely cheap, this optimization may provide no practical benefit.
Scenario 3: Stabilizing Event Handler Functions
One of the most common uses of useCallback is keeping event handler references stable when passing them to child components.
For example:
const handleProductClick = useCallback((productId) => {
setSelectedProduct(productId);
analytics.track('product_click', { productId });
}, [analytics]);
JavaScriptThen:
products.map(product => (
<ProductCard
key={product.id}
product={product}
onClick={handleProductClick}
/>
));
JavaScriptIf ProductCard uses:
const ProductCard = React.memo(function ProductCard({
product,
onClick
}) {
// ...
});
JavaScripta stable onClick reference can reduce extra renders caused by changes in the function reference.
You need to pay particular attention to the dependency array here.
If:
analytics
JavaScriptcomes from a prop, state, context, or another reactive value in the component scope, it should be included in the dependency array:
[analytics]
JavaScriptMeanwhile:
setSelectedProduct
JavaScriptif it comes from:
const [selectedProduct, setSelectedProduct] = useState(null);
JavaScripthas a stable function reference guaranteed by React, so it can normally be omitted.
If analytics is a stable singleton imported at module scope:
import analytics from './analytics';
JavaScriptthen it is not a reactive dependency inside the component, and you can write:
const handleProductClick = useCallback((productId) => {
setSelectedProduct(productId);
analytics.track('product_click', { productId });
}, []);
JavaScriptIn real projects, use ESLint's react-hooks/exhaustive-deps rule to validate dependencies rather than manually removing them based on intuition.
Common Misconceptions
Misconception 1: Every Function Should Use useCallback
A common mistake is:
const handleClick = useCallback(() => {
setCount(count + 1);
}, [count]);
JavaScriptand then writing every function in the component this way.
In practice, if:
- The function is not passed to a memoized child component.
- It is not used as a dependency of another Hook.
- Creating the function is cheap.
- The child component itself renders very quickly.
then using useCallback will often provide no practical value.
Misconception 2: Every Calculation Should Use useMemo
For example:
const fullName = useMemo(() => {
return `${firstName} ${lastName}`;
}, [firstName, lastName]);
JavaScriptThis calculation is extremely simple and generally does not need useMemo.
Writing it directly:
const fullName = `${firstName} ${lastName}`;
JavaScriptis usually clearer.
Misconception 3: If Object Contents Are the Same, React Treats the Dependency as Unchanged
For example:
const filters = {
keyword,
category
};
JavaScriptEven if:
filters.keyword
filters.category
JavaScripthave exactly the same values across two renders, creating a new object means:
previousFilters !== currentFilters
JavaScriptso React will consider the dependency changed.
Therefore, do not focus only on an object's contents. Also consider whether its reference is stable.
Misconception 4: useCallback Always Reduces Child Component Renders
It does not.
Suppose:
const handleClick = useCallback(() => {
// ...
}, []);
JavaScriptAlthough handleClick is stable, the child may still receive other props that change on every render:
<Child
onClick={handleClick}
options={{ theme: 'dark' }}
/>
JavaScriptHere:
options={{ theme: 'dark' }}
JavaScriptcreates a new object on every render.
Therefore, even though onClick has not changed, React.memo may still re-render the child because options changed.
Misconception 5: React.memo Guarantees That a Component Will Not Re-render
It does not.
React.memo is a performance optimization mechanism.
By default, it compares a component's previous and next props. If React determines that the props are equal, it can usually skip a re-render caused by a parent update.
However, the component can still re-render because of:
- Changes to its own state.
- Context changes.
- Other internal React scheduling behavior.
Therefore, do not treat React.memo as a semantic guarantee that a component "will not re-render."
The Relationship Between useMemo and useCallback
Conceptually:
useCallback(fn, dependencies)
JavaScriptcan be understood as being close to:
useMemo(() => fn, dependencies)
JavaScriptThe difference lies in how the APIs express their intent.
With useMemo:
const value = useMemo(() => {
return calculateSomething();
}, [dependencies]);
JavaScriptwhat gets cached is:
the return value of calculateSomething()
With useCallback:
const callback = useCallback(() => {
doSomething();
}, [dependencies]);
JavaScriptwhat gets cached is:
the function reference
Therefore:
useMemo
↓
Focus on the "value"
useCallback
↓
Focus on the "function"
This is the simplest way to understand the difference between the two.
How to Decide Whether You Should Use Them
In real projects, you can use the following guidelines.
Should You Use useMemo?
Consider using it when:
- The computation is clearly expensive.
- The dataset is large.
- The computation is repeated frequently.
- The result is an object or array whose reference stability affects downstream components.
- The Profiler shows that the calculation has a measurable cost.
You usually do not need it for:
- Simple string concatenation.
- Simple arithmetic.
- Lightweight processing of small arrays.
- Cached results that do not affect any performance-sensitive component.
Should You Use useCallback?
Consider using it when:
- The function is passed as a prop to a child component wrapped with
React.memo. - The function is a dependency of another Hook.
- A third-party component depends on a stable callback reference.
- A custom Hook explicitly requires a stable function reference.
- The Profiler shows that changing function references causes meaningful extra rendering.
You usually do not need it when:
- The function is only used inside the current component.
- The child component is not memoized.
- The child component is very cheap to render.
- The callback's dependencies change on nearly every render, making the cache almost impossible to reuse.
Performance Analysis Matters More Than Blind Optimization
Developers often see:
filter
map
sort
function
JavaScriptand instinctively add:
useMemo
useCallback
JavaScriptThat is not necessarily the right approach.
A more reasonable optimization workflow is:
First write correct, clear code
↓
Use performance profiling tools
↓
Identify the real bottleneck
↓
Optimize the specific problem
↓
Measure again
↓
Verify that the optimization works
The React DevTools Profiler can show you:
- Which components re-rendered.
- How long each render took.
- Which updates triggered component renders.
The Performance panel in Chrome DevTools can also help you inspect:
- JavaScript execution time.
- Main Thread blocking.
- Long tasks.
- Browser rendering activity.
- Flame charts.
These tools are usually more valuable than blindly adding useMemo and useCallback.
How React Compiler Affects Manual Memoization
As React's concurrent features, Server Components, and React Compiler continue to evolve, React's performance optimization strategies are evolving as well.
In projects where React Compiler is enabled, the compiler can automatically perform many memoization optimizations that developers previously had to implement manually with:
React.memo
useMemo
useCallback
Therefore, in new projects that use React Compiler, you should not mechanically add useMemo or useCallback to every value and function.
A better approach is to consider:
- Whether the project has React Compiler enabled.
- Whether the current component actually performs repeated calculations.
- Whether reference changes really cause expensive child component re-renders.
- Whether there are special cases that require manually controlling reference stability.
- Actual performance data from the Profiler.
Even so, understanding useMemo, useCallback, and React.memo remains very important.
That is because they reflect several core concepts in React performance optimization:
Reference stability
+
Dependencies
+
Component re-renders
+
Computation cost
These concepts remain fundamental to understanding React's rendering model and performance issues.
Summary
The core difference between useMemo and useCallback can be summarized as:
useMemo
↓
Cache a computed result
↓
Reduce repeated computation
useCallback
↓
Cache a function reference
↓
Reduce unnecessary reference changes
A useful rule to remember is:
Do not use them simply because you can. Use them when there is a clear performance or reference-stability requirement.
For useMemo:
Focus on computation cost
For useCallback:
Focus on whether the function reference affects other components or Hooks
And for any performance optimization:
Measure first
Optimize second
Verify again afterward
This is usually more reliable than blindly adding memoization.