React Compiler in 2026: Should You Stop Using useMemo and useCallback?
Author
Muhammad Awais
Published
May 30, 2026
Reading Time
10 min read
Views
24k

You know the feeling. A component re-renders one too many times, so you wrap something in useMemo. Then the linter complains about the dependency array. So you add a function to it. But that function gets recreated on every render, so you reach for useCallback. That useCallback depends on a prop that's an object, which has a new reference every render, so you wrap that in useMemo too. Twenty minutes later, half your component is optimization boilerplate and you've lost track of what you were actually building.
In 2026, that entire loop is mostly behind us. React Compiler v1.0 stable since October 2025, now the default in Next.js 16 handles memoization automatically at build time. But "mostly" is doing a lot of work in that sentence. Knowing exactly when to delete your useMemo calls and when to keep them is what this guide is about.
What React Compiler actually does under the hood
How to enable it in Next.js and Vite today
Which
useMemoanduseCallbackcalls you can safely deleteThe edge cases where manual memoization still matters in 2026
How to migrate an existing codebase without breaking things
What React Compiler Actually Does
React Compiler (previously called React Forget during its multi-year development) is a build-time tool that analyzes your component code and automatically inserts memoization where React's rendering model would otherwise cause unnecessary work. The key phrase is build time this is a compiler transformation, not a runtime hook.
Before the compiler, React's rendering model was straightforward but expensive: if state changed in a parent component, that component and every child component would re-render, regardless of whether their props had actually changed. The fix was manual: wrap components in React.memo, wrap values in useMemo, wrap callbacks in useCallback. Miss one and you get unnecessary re-renders. Add too many and you get a codebase that reads more like performance annotations than actual product code.
The compiler changes this by analyzing the data flow inside your components at compile time. It knows which values depend on which state, inserts memoization automatically for the values that need it, and skips it for values that don't. The output JavaScript looks similar to what a very thorough developer would write by hand except the compiler never forgets a dependency and never guesses.
The practical result: in a codebase I audited recently, enabling the compiler and removing redundant manual memoization cut about 400 lines of optimization boilerplate. The logic was still there it was just handled by the build tool instead of scattered through every component file.
How to Enable React Compiler in Next.js and Vite
If you're on Next.js 15 or 16, enabling the compiler is a one-line config change. Open your next.config.ts and add:
// next.config.ts
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
experimental: {
reactCompiler: true,
},
};
export default nextConfig;For Vite-based projects (including TanStack Start), install the Babel plugin and update your Vite config:
npm install --save-dev babel-plugin-react-compiler// vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
export default defineConfig({
plugins: [
react({
babel: {
plugins: [["babel-plugin-react-compiler"]],
},
}),
],
});That's it for most projects. Run your dev server, open React DevTools, and you'll see the compiler's memoization applied automatically. No other changes needed to get the baseline benefits.
If you want to verify the compiler is working correctly on a specific component, the React DevTools Profiler now shows compiler-inserted memoization distinctly from manual memoization look for the "auto" label on memoized values in the flame graph.
What You Can Safely Delete Right Now
This is what most developers actually want to know. Here's the practical breakdown based on real migration work in 2026.
Safe to delete in 95%+ of cases:
useMemo for derived values from state/props : filtering a list, sorting an array, computing a formatted string. The compiler handles all of these. Before:
const sorted = useMemo(() => [...items].sort(...), [items]). After:const sorted = [...items].sort(...). Same performance, cleaner code.useCallback for event handlers passed to child components : the classic "I'm passing onClick to a memoized child" pattern. The compiler analyzes the reference chain and handles stability automatically.
React.memo wrappers on most components : if the compiler is managing the memoization graph correctly,
React.memobecomes redundant for most components. The compiler already knows which props changed.useMemo for JSX subtrees : wrapping a complex rendering block in
useMemoto avoid re-computation was a valid optimization before. The compiler does this analysis automatically.
The migration path is simple: enable the compiler, run your test suite, then start removing manual memoization component by component. If tests pass and the Profiler shows no regression, the removal is safe.
Use our regex tester to find all useMemo and useCallback instances across your codebase quickly a pattern like use(Memo|Callback)( will highlight every occurrence before you start the cleanup.
When Manual Memoization Still Matters in 2026
Here's the part most "useMemo is dead" articles skip. There are real edge cases where removing manual memoization breaks things even with the compiler enabled. I hit two of them during migration work.
Third-party libraries that depend on reference equality: Libraries like react-hook-form, react-dnd, and some animation libraries internally compare function references to decide whether to re-run effects or re-register handlers. The compiler can't see inside these libraries' internals, so it can't know that reference stability matters there. If you remove a useCallback that wraps a drag handler registered with react-dnd, drag-and-drop breaks even if the compiler "handled" the memoization from React's perspective.
// Keep this — react-dnd depends on reference equality
const handleDrop = useCallback((item: DragItem) => {
dispatch({ type: "DROP", payload: item });
}, [dispatch]);
// Safe to remove — pure derived value, no library dependency
// const label = useMemo(() => item.name.toUpperCase(), [item.name]);
const label = item.name.toUpperCase();Extremely expensive computations: The compiler's auto-memoization is smart, but it's designed for typical UI operations. If you're running something genuinely expensive parsing a large dataset, running a client-side algorithm over thousands of items, computing cryptographic values explicit useMemo with clear intent is still valid. It communicates to the next developer that this computation is intentionally cached, not accidentally left there from before the compiler era.
Custom hooks that expose stable references as part of their API contract: If you're building a library or a shared hook that promises referential stability to consumers, manual useCallback is still appropriate. The consumer of your hook shouldn't have to know whether it uses the compiler or not.
The rule of thumb: delete manual memoization by default, keep it only when you have a specific reason that the compiler can't know about external library contracts, documented performance requirements, or public API stability guarantees.
Before and After: Real Component Migration
Here's what a typical pre-compiler component looks like, and what it becomes after migration:
// BEFORE — pre-compiler, 2024-style
const ProductList = React.memo(({ items, onSelect, searchTerm }) => {
const filteredItems = useMemo(
() => items.filter(item =>
item.name.toLowerCase().includes(searchTerm.toLowerCase())
),
[items, searchTerm]
);
const sortedItems = useMemo(
() => [...filteredItems].sort((a, b) => a.price - b.price),
[filteredItems]
);
const handleClick = useCallback(
(id: string) => { onSelect(id); },
[onSelect]
);
return (
<ul>
{sortedItems.map(item => (
<ProductCard key={item.id} item={item} onClick={handleClick} />
))}
</ul>
);
});// AFTER — compiler handles it, 2026-style
const ProductList = ({ items, onSelect, searchTerm }) => {
const filteredItems = items.filter(item =>
item.name.toLowerCase().includes(searchTerm.toLowerCase())
);
const sortedItems = [...filteredItems].sort((a, b) => a.price - b.price);
return (
<ul>
{sortedItems.map(item => (
<ProductCard key={item.id} item={item} onClick={() => onSelect(item.id)} />
))}
</ul>
);
};Same performance characteristics. About 60% less code. The compiler inserted the memoization at build time it's in the output JavaScript, just not visible in your source. This is what the "write straightforward code" promise actually looks like in practice.
For more React performance wins in Next.js, our guide on fixing unnecessary re-renders in 2026 covers complementary patterns that work alongside the compiler.
The Migration Strategy for Existing Codebases
Don't try to remove all manual memoization in one pass. Here's the approach that works in production without introducing regressions:
First, enable the compiler in your config but don't change any code yet. Run your full test suite and check the React DevTools Profiler on your most complex pages. If the compiler causes any conflicts with existing manual memoization, it'll surface here though in practice, the compiler is designed to coexist with manual memoization safely.
Second, pick one component file ideally a leaf component with no third-party library dependencies. Remove all useMemo, useCallback, and React.memo from it. Run tests, check the Profiler, verify the component behaves correctly. If it does, commit that change and move on.
Third, work component by component through your codebase, starting from leaf components and moving toward more complex container components. The ESLint rule react-compiler/react-compiler will flag components the compiler can't fully optimize these are your signal that manual intervention may still be needed.
Fourth, handle the third-party library edge cases last. By the time you've cleaned up the majority of your codebase, you'll have a clear picture of which libraries need special treatment.
Need to track the files you've migrated? Our Markdown to HTML converter is useful for maintaining migration checklists in your team's documentation write in Markdown, convert to HTML for internal wikis instantly.
What This Means for New Projects in 2026
If you're starting a new React or Next.js project today, the answer is simple: enable React Compiler from day one and don't teach yourself the manual memoization patterns at all. Write straightforward components. Let the compiler handle performance. Only reach for useMemo or useCallback when you have a documented, specific reason that the compiler can't address.
This isn't just about less code it's about where mental energy goes. Before the compiler, a meaningful chunk of senior React developer time went into memoization strategy: deciding what to wrap, debugging stale closures, explaining dependency arrays to junior developers. That cognitive overhead is now largely gone. Teams can focus on product logic, not rendering mechanics.
The React team's original vision for React was always "write UI as functions of state." Manual memoization was always a leaky abstraction a workaround for a rendering model that needed developer assistance to perform well. React Compiler is what closes that loop. In 2026, React finally works the way it was always supposed to.
Ready to clean up your codebase? Check out our React Server Components guide for the complementary architectural shift that works alongside the compiler for maximum Next.js performance.
Frequently Asked Questions
Is React Compiler stable and production-ready in 2026?
Yes. React Compiler reached v1.0 in October 2025 and is now the default in Next.js 16. It has been in gradual rollout across Meta's production applications since before its public release, which means it's been battle-tested at significant scale. For new projects, enabling it from day one is recommended. For existing projects, the migration is safe and reversible you can opt individual components out with the "use no memo" directive if needed.
Do I need to delete all my useMemo and useCallback right now?
No. existing manual memoization doesn't conflict with the compiler. Your current code will continue to work. The cleanup is a quality-of-life improvement, not an urgent fix. The compiler and manual memoization coexist safely; redundant manual memoization just adds a tiny bit of overhead from the double-wrapping, which is negligible in practice. Clean it up gradually as you touch files, not all at once.
What happens to useMemo and useCallback that I keep in my code?
They still work exactly as before. The compiler is additive it doesn't remove or override your manual memoization, it supplements it. If you have a useMemo in a component, the compiler respects it. You'll just have both the compiler's automatic memoization and your manual memoization running, which is slightly redundant but not harmful. The React DevTools will show both layers distinctly.
How do I know if the React Compiler is actually working in my project?
Open React DevTools and go to the Components panel. Components optimized by the compiler show a "Memo ✨" badge the sparkle distinguishes compiler-inserted memoization from manual React.memo. You can also use the Profiler to compare render counts before and after enabling the compiler on a complex page you should see fewer renders for components that weren't previously memoized manually.
Are there cases where the React Compiler makes performance worse?
In theory, the compiler could over-memoize in some edge cases adding memoization overhead to values that change frequently and would be cheaper to just recompute. In practice this is rare and the team has tuned the heuristics carefully. If you suspect a performance regression after enabling the compiler, use the React Profiler to compare flame graphs before and after, and use the "use no memo" opt-out directive on the specific component if you find an issue.
Does React Compiler work with TypeScript?
Yes, fully. React Compiler has first-class TypeScript support. The type inference in the compiler's analysis uses TypeScript type information where available to make better memoization decisions. TypeScript strict mode is recommended it gives the compiler more information to work with and results in more accurate automatic optimization. There are no TypeScript-specific configuration changes needed beyond what you'd normally set up.
Are all WebToolsHub tools free to use?
Yes. every tool on WebToolsHub is completely free, requires no account or sign-up, and runs entirely in your browser. No data is ever sent to a server. The regex tester, markdown converter, JSON to TypeScript tool, and all other tools process everything client-side on your machine.
Continue Reading
View All HubLevel Up Your Workflow
Free professional tools mentioned in this article
Unix Timestamp Converter
Convert Unix timestamps to readable dates and back instantly. View the current epoch time, convert any timestamp, and see results in any timezone.
JWT Secret Key Generator
Generate cryptographically secure, high-entropy JWT secret keys instantly. A free, client-side CSPRNG key generator for secure HS256 and HS512 tokens.
HTML to JSX / TSX Converter
Instantly convert HTML code to React JSX or TSX components. Automatically handles className, style objects, SVGs, and self-closing tags with secure, in-browser processing.
Fancy Font & Stylish Text Generator
Transform your text into 50+ stylish and aesthetic fonts instantly. Perfect for Instagram bios, TikTok captions, and PUBG nicknames. One-click copy & paste.




