useMemo and useCallback in Modern React: Are They Still Worth Using?
React performance optimization is evolving. In 2026, do we still need to manually memoize everything, or is it time to rethink our approach?
For years, one of the most common recommendations in the React ecosystem was to use useMemo and useCallback to prevent unnecessary calculations and renders.
This advice became so widespread that many developers started using these hooks almost everywhere, regardless of whether a real performance problem existed.
But React has evolved. With the introduction of React Compiler, many optimization tasks that previously required manual memoization can now be handled automatically.
So, here is the question: Do we still need useMemo and useCallback in modern React and Next.js, or can we write efficient code without relying on them everywhere?
1. First, What Do These Hooks Actually Do?
useMemo — Caching a Calculation Result
useMemo lets you cache the result of a calculation and reuse it during subsequent renders, as long as its dependencies remain unchanged.
import { useMemo } from 'react';
function ProductList({ products, category }) {
const filteredProducts = useMemo(() => {
return products.filter(
(product) => product.category === category
);
}, [products, category]);
return (
<ul>
{filteredProducts.map((product) => (
<li key={product.id}>
{product.name}
</li>
))}
</ul>
);
}In this example, the filter() calculation runs again when products or category changes. If neither dependency changes, React can reuse the previously calculated result.
This can be useful when working with large arrays, expensive calculations, or values whose referential stability matters to child components.
However, if the calculation is simple, such as adding two numbers, using useMemo is usually unnecessary.
useCallback — Caching a Function Reference
useCallback caches a function reference between renders, as long as its dependencies remain unchanged.
import { useCallback, memo } from 'react';
const ProductButton = memo(function ProductButton({
onSelect,
}) {
console.log('ProductButton rendered');
return (
<button onClick={onSelect}>
Select product
</button>
);
});
function ProductPage({ productId }) {
const handleSelect = useCallback(() => {
console.log('Selected:', productId);
}, [productId]);
return <ProductButton onSelect={handleSelect} />;
}Here, useCallback can be useful because ProductButton is wrapped in memo. When the parent renders again but productId remains unchanged, the stable callback reference allows React to skip an unnecessary render of ProductButton.
One important distinction: useCallback does not prevent a function from being created during rendering. Instead, it preserves the returned function reference when its dependencies remain the same.
2. Why Were These Hooks So Important?
React normally calls a component function again when that component renders. This is not inherently a problem — in many cases, rendering is relatively inexpensive.
Performance issues can arise when:
A component performs expensive calculations.
Large component trees render frequently.
Newly created objects and functions prevent
memofrom working effectively.An interface handles frequent interactions and complex state updates.
In these situations, useMemo, useCallback, and React.memo give developers more control over certain unnecessary calculations and renders.
However, using them incorrectly or unnecessarily can make code more complicated, introduce additional dependency management, and provide little or no measurable performance improvement.
3. What Has React Compiler Changed?
This is the most important part of the discussion.
React Compiler analyzes your code and can automatically apply memoization in many situations. Its goal is to reduce the need to manually write useMemo, useCallback, and React.memo.
In other words, instead of writing:
const filteredItems = useMemo(
() => filterItems(items, query),
[items, query]
);you may be able to write simpler code:
const filteredItems = filterItems(items, query);With React Compiler enabled and applicable to the code, the compiler can handle the necessary optimization automatically.
This does not mean every function will automatically become faster in every situation. The result depends on the code, compiler configuration, and which optimizations are possible.
React's official guidance encourages developers to rely on React Compiler for supported automatic memoization rather than adding manual memoization everywhere.
4. What About Next.js?
Next.js is built on React, so React Compiler support is relevant here as well.
Recent Next.js versions support enabling React Compiler through the appropriate configuration and required plugin.
For example:
import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
reactCompiler: true,
};
export default nextConfig;Before using this configuration, check the documentation for your installed Next.js version and install the required babel-plugin-react-compiler package.
One important detail: using a recent version of Next.js does not automatically mean React Compiler is enabled. You need to verify your project's actual configuration.
The distinction between Server Components and Client Components also matters.
Server Components: They render on the server and do not need client-side
useMemooruseCallbackfor their normal rendering.Client Components: They can use React hooks and may benefit from the optimizations provided by React Compiler.
'use client': Adding this directive does not mean every value or function needs to be memoized.
In Next.js, the first step toward optimization should be understanding how a component works, not adding hooks everywhere.
5. When Are useMemo and useCallback Still Useful?
Despite automatic optimizations, these hooks remain useful in certain situations.
When a calculation is genuinely expensive
const statistics = useMemo(() => {
return calculateComplexStatistics(data);
}, [data]);If the calculation is expensive and data does not change frequently, memoization may help. However, you should first confirm that this calculation is actually responsible for the performance problem.
When referential stability matters
If a value is passed to a child component wrapped in memo, keeping its reference stable can help prevent unnecessary renders.
The same applies to callbacks. useCallback can be useful when a stable function reference matters to a child component or to another hook's dependencies.
When you need specific control over behavior
Manual memoization can still be useful even when React Compiler is available. Certain effect dependencies and specialized integrations may benefit from it.
However, consider whether the code can be simplified first. Sometimes the better solution is not to add useMemo, but to move an object inside a useEffect instead of making that object a dependency.
6. When Should You Avoid These Hooks?
You can often skip them when:
The calculation is simple and inexpensive.
A callback is passed to a regular child component that is not memoized.
There is no measurable performance problem.
A hook is being added simply because it is considered a best practice.
The same problem can be solved with simpler code.
For example:
const fullName = `${firstName} ${lastName}`;Using useMemo for such a simple expression is usually unnecessary.
Similarly:
const handleClick = () => {
setOpen(true);
};There is generally no need to wrap this function in useCallback unless its reference stability serves a specific purpose.
Also, remember that memoization should never be used as a guarantee of correctness. Your application logic should remain correct even when an optimization cache is unavailable.
7. How Would I Approach a New React Project Today?
If I were starting a new React or Next.js project today, my approach would be straightforward:
Start with simple, readable code instead of adding memoization everywhere.
Evaluate React Compiler if the project meets its requirements.
Use React DevTools Profiler to identify actual rendering and performance issues.
Simplify component structure, state management, and calculations before adding more optimizations.
Introduce
useMemooruseCallbackmanually only when there is a clear reason.In existing projects, avoid removing every memoization hook at once. Make changes incrementally and measure the results.
This approach helps avoid premature optimization without giving up tools that can provide real benefits.
Conclusion
useMemo and useCallback are not obsolete. However, our approach to using them should change.
In the past, developers often had to perform manually many optimizations that React Compiler can now handle automatically. This means modern React developers need to understand not only the syntax of these hooks, but also the problems they solve and whether they are necessary in a particular situation.
My guiding principle would be:
Write simple code, measure performance, and optimize only what actually needs it.
Because good React code is not simply code with fewer renders. It is code that works correctly, remains easy to maintain, and can be optimized effectively when necessary.