TL;DR: React Compiler can automatically optimize many memoization patterns that it can safely analyze, reducing the need for routine useMemo, useCallback, and React.memo usage. Learn what the compiler optimizes, where manual memoization still adds value, how performance best practices change, and how to enable React Compiler in Next.js and Vite projects.
You open a React component and see a familiar pattern:
- A couple of
useMemocalls - Several
useCallbackhooks - A
React.memowrapper around a child component
Everything looks optimized. Yet the component is still difficult to read, nobody wants to touch the dependency arrays, and it’s not even clear whether any of those optimizations are helping.
For years, this was normal React development.
If a value was an object, we wrapped it in useMemo. If a function was passed to a child component, we reached for useCallback. If a component re-rendered too often, we added React.memo and hoped for the best.
React Compiler changes that workflow.
Instead of manually sprinkling memoization throughout your application, React Compiler can automatically optimize many common memoization patterns that it can safely analyze during the build process. The result is simpler components, fewer dependency arrays, and less performance-related boilerplate.
But does that mean useMemo, useCallback, and React.memo are officially obsolete?
Not quite.
The real answer is more practical than the headlines suggest.
Why manual memoization became a habit
Before React Compiler, developers often optimized for reference stability rather than real performance problems.
They commonly used:
-
useMemoto cache calculated values. -
useCallbackto keep function references stable. -
React.memoto prevent child components from re-rendering when props were unchanged.
A typical component slowly turns into this:
JavaScript
import { useMemo, useCallback } from "react";
function ProductSearch({ products, query, onSelect }) {
const visibleProducts = useMemo(() => {
return products.filter((product) =>
product.name.toLowerCase().includes(query.toLowerCase())
);
}, [products, query]);
const handleSelect = useCallback(
(id) => {
onSelect(id);
},
[onSelect]
);
return (
<ProductList
products={visibleProducts}
onSelect={handleSelect}
/>
);
}
There’s nothing wrong with this approach.
The problem is that many teams adopted it by default, even when no measurable performance benefit existed.
Over time, components became harder to maintain because developers had to:
- Keep dependency arrays accurate
- Avoid stale closures
- Understand memoization behavior across the component tree
- Review optimization code that often delivered little value
The end result was frequently more complexity than performance.
What is React Compiler?
React Compiler is a build-time optimization tool that analyzes React components and automatically applies memoization when it can safely prove that doing so won’t change behavior.
In practical terms, it allows developers to write straightforward React code while letting the compiler handle many of the optimization decisions behind the scenes.
Consider this example:
JavaScript
function ProductSearch({ products, query, onSelect }) {
const visibleProducts = products.filter((product) =>
product.name.toLowerCase().includes(query.toLowerCase())
);
const handleSelect = (id) => {
onSelect(id);
};
return (
<ProductList
products={visibleProducts}
onSelect={handleSelect}
/>
);
}
Without a compiler, many developers would automatically add useMemo and useCallback.
With React Compiler enabled, that extra code is often unnecessary.
The important detail is that React Compiler only optimizes patterns it can safely analyze. If a component violates React rules or contains patterns the compiler cannot guarantee, it simply skips those optimizations.
That means the goal isn’t to stop thinking about performance. The goal is to stop optimizing everything preemptively.
When React Compiler can make manual useMemo and useCallback unnecessary
React Compiler is particularly effective in situations where developers previously used memoization as a precaution rather than a necessity.
Derived values
A common example is calculating filtered or transformed data from props.
JavaScript
function InvoiceSummary({ invoices, status }) {
const filteredInvoices = invoices.filter(
(invoice) => invoice.status === status
);
const total = filteredInvoices.reduce(
(sum, invoice) => sum + invoice.amount,
0
);
return <Summary total={total} invoices={filteredInvoices} />;
}
Before React Compiler, many developers would wrap filteredInvoices and total in useMemo.
With React Compiler enabled, this kind of pure derived value may be a good candidate for automatic optimization. The compiler’s ability to optimize it depends on whether the calculation is pure and follows patterns the compiler supports. Not every derived value is guaranteed to be cached.
Inline event handlers
Another common pattern is wrapping every event handler with useCallback.
JavaScript
function TodoItem({ todo, onToggle }) {
const handleChange = () => {
onToggle(todo.id);
};
return (
<label>
<input
type="checkbox"
checked={todo.completed}
onChange={handleChange}
/>
{todo.title}
</label>
);
}
Many teams previously used useCallback here solely to preserve function identity.
In compiler-enabled applications, this level of manual optimization is often unnecessary.
Lightweight object props
Developers also frequently memoized simple configuration objects.
JavaScript
function ChartPanel({ data, theme }) {
const chartOptions = {
color: theme.primaryColor,
showLegend: true,
};
return <RevenueChart data={data} options={chartOptions} />;
}
React Compiler can often optimize these scenarios without requiring additional hooks.
When you still need useMemo
React Compiler reduces routine memoization, but it does not eliminate legitimate performance bottlenecks.
Use useMemo when:
- A calculation is genuinely expensive
- Profiling shows measurable render costs
- Inputs change far less frequently than renders
Caching improves user-visible performance
JavaScript
function AnalyticsView({ events }) {
const report = useMemo(() => {
return buildLargeReport(events);
}, [events]);
return <ReportTable report={report} />;
}
Even if buildLargeReport() processes thousands of records and performs grouping, sorting, and aggregation, an expensive calculation does not automatically require manual memoization. React Compiler may already optimize the calculation when it can safely analyze the code. If profiling shows that the calculation is still a bottleneck, keeping useMemo may be appropriate.
The key difference in 2026 is intent.
- You’re no longer adding
useMemobecause a value is an array or object. - You’re adding it because you’ve identified an actual problem.
Read the full blog post on the Syncfusion Website
Top comments (0)