React Compiler - How It Works
React Compiler (babel-plugin-react-compiler, stable 1.0 since 7 Oct 2025) is a build-time Babel plugin that rewrites each component and hook so it memoizes its own intermediate…
- Published
- Reading time
- 15 min read
What it is, and where it sits in the toolchain
The compiler is "an optimizing compiler that optimizes components and hooks through automatic memoization". It ships as a Babel plugin, but the core is "largely decoupled from Babel": it lowers Babel's AST into its own High-level Intermediate Representation (HIR) and analyses data flow and mutability on that.1 It is a plain dev dependency that runs while you bundle. Nothing extra ships to the browser apart from a tiny runtime import, and it must be the first plugin in your Babel pipeline because it needs the original source shape for its analysis.2 Vite, Next.js (through an swc integration), Expo/Metro, Rsbuild and Rspack all expose it as a switch.23
The same core powers the lint rules in eslint-plugin-react-hooks: the ESLint plugin effectively runs the compiler and reports a subset of its diagnostics.4
flowchart LR
src["App.tsx (your JSX)"] --> babel["Babel / swc / Vite plugin"]
subgraph babel_pass["babel-plugin-react-compiler (runs FIRST)"]
pick["Program visitor: pick components/hooks<br/>(compilationMode, directives)"] --> pipe["Pipeline.ts: HIR → SSA → inference → scopes → codegen"]
end
babel --> babel_pass --> rest["other plugins: JSX → jsx(), TS strip, ..."] --> bundle["bundle.js<br/>import {c as _c} from 'react/compiler-runtime'"]
bundle --> rt["runtime: _c(n) = useMemoCache(n)<br/>(ReactFiberHooks.js)"]The plugin object is named react-forget, the project's old codename, and does all its work from a Program.enter visitor so it sees the code before any other plugin has rewritten it.5 For each function in the file it decides whether this is a component or hook to compile, calls the compiler pipeline, and swaps in the returned AST node.4
Which functions get compiled: compilationMode and directives
getReactFunctionType makes the decision.6
The opt-in directives are "use memo" / "use forget", and the opt-out directives are "use no memo" / "use no forget".7 Whatever the mode, "use no memo" always wins.8 One consequence of 'infer': plain helper functions such as expensiveSum() below are not compiled. The compiler memoizes the call to them inside a component, but the helper itself is untouched, and its memoization is not shared across components.3
The pipeline (Entrypoint/Pipeline.ts)
runWithEnvironment is the real pass list. It is a long, straight-line function, and every pass logs its output under a name. Those names are what you see in the playground's IR tabs.9 The compiler's own design doc describes it in the same phases.4 Here are the passes that matter, in order:
1. Lowering to HIR: lower()
Lowering turns the Babel AST into a control-flow graph of basic blocks. Each block holds instructions and ends in a terminal. The IR keeps exact JS evaluation order and resolves break/continue targets. It is "high-level" because it still knows the difference between if, ternary and &&, and between for and for..of, so codegen can emit code that looks like what you wrote.1011 Why a CFG and not AST rewriting? Memoization needs data-flow facts ("is x mutated after line 5? on which paths?"), and those are textbook CFG analyses. Early prototypes worked on the AST, and the switch to an HIR is what made precise analysis possible.1
Before SSA there are cleanup passes: pruneMaybeThrows, validateUseMemo, dropManualMemoization (turns your useMemo/useCallback into plain code, keeping markers so it can later check that it preserved your guarantees), IIFE inlining, and block merging.12
2. SSA: enterSSA, eliminateRedundantPhi
Every assignment gets a fresh identifier, and control-flow joins get phi nodes.13 After this pass, "which definition of x reaches this use?" has a single answer, which the later inference passes rely on. constantPropagation follows.
3. Type inference and hook validation: inferTypes, validateHooksUsage
inferTypes is deliberately conservative. It identifies only what matters for later passes: which values are hooks, useState setters, refs, primitives, JSX, and so on.144 Right after it, validateHooksUsage and validateNoCapitalizedCalls run. A conditional hook call fails here.15
4. Effect / aliasing inference: analyseFunctions, inferMutationAliasingEffects, inferMutationAliasingRanges
This is the core of the compiler. inferMutationAliasingEffects interprets every instruction against an abstract heap, recording effects such as Create, Capture, Alias, Mutate and Freeze. Values that flow into JSX or a hook become frozen, and props and hook results are frozen from the start. Mutating a frozen value is the "This value cannot be modified" error.1617 inferMutationAliasingRanges then computes, for each value, its mutable range: the span of instructions from creation to last mutation, extended through aliases.18 The mutable range is the key concept. A value can only be cached as a unit once it is done being built, so everything that mutates it has to sit inside the same memo block.
Then come the render-purity validations: refs read during render, setState called unconditionally in render, locals reassigned after render, and others.19
5. Reactivity: inferReactivePlaces
This pass marks which values can change between renders: props, state, context, hook results, and anything derived from them.20 A useState setter is known to be stable, so it is not reactive. That is why handleSelect in the example below gets a dependency-free, sentinel-only cache slot.
6. Reactive scope construction and merging
inferReactiveScopeVariablesputs operands that mutate together into disjoint sets. Each set becomes a reactive scope, the futureif (...) {...}memo block.21alignReactiveScopesToBlockScopesHIRandmergeOverlappingReactiveScopesHIRstretch and merge scopes so they nest properly inside JS block structure. You cannot emit anifthat starts inside one branch and ends in another.22buildReactiveScopeTerminalsHIRmakes the scopes explicit in the CFG.flattenScopesWithHooksOrUseHIRremoves any scope that contains a hook orusecall, because memoizing it would make the hook call conditional.23 This is whyuseStatecalls in compiled output always sit outsideif ($[i] ...)blocks.propagateScopeDependenciesHIRcomputes each scope's dependencies, the reactive values it reads (down to property paths likeprops.items.length), which become the$[i] !== depchecks.24
7. ReactiveFunction and pruning
buildReactiveFunction turns the CFG back into a tree, a hybrid of HIR and AST.25 Then the cost/benefit passes run:
pruneNonEscapingScopes: if a value never escapes (it isn't returned, isn't reachable from the return value, isn't passed to a hook), caching it buys nothing, so its scope is removed.26pruneNonReactiveDependenciesandpruneUnusedScopes.mergeReactiveScopesThatInvalidateTogether: when scopes have identical dependencies, or one scope's output is the next one's only input, they always invalidate together, so they are merged to save slots and comparisons.27pruneAlwaysInvalidatingScopes: an unmemoized object or array literal is new on every render, so memoizing anything downstream of it is wasted and gets removed.28- Then
propagateEarlyReturns,promoteUsedTemporaries,renameVariables, andvalidatePreservedManualMemoization, which checks that it kept the guarantees of anyuseMemoyou wrote.
8. Codegen: codegenFunction
If any slots were used, codegen emits const $ = _c(<count>) at the top.29 For each scope it emits $[i] !== dep comparisons joined with ||.30 A scope with no dependencies gets a one-time init check instead: $[k] === Symbol.for("react.memo_cache_sentinel").31 The import comes from react/compiler-runtime for target '19', or from the react-compiler-runtime npm package for '17'/'18'.32
Real compiler output, annotated
I compiled the example below with babel-plugin-react-compiler@1.0.0 and @babel/core through a small script, with JSX left in place so the result stays readable. The script and the outputs live in the scratchpad (compile-demo/compile.mjs, examples/App.jsx, out-App.js).
Input
import { useState } from "react";
function expensiveSum(items) {
let total = 0;
for (const item of items) total += item.price;
return total;
}
export function List({ items, onSelect }) {
return (
<ul>
{items.map((item) => (
<li key={item.id} onClick={() => onSelect(item.id)}>{item.name}</li>
))}
</ul>
);
}
export function Counter({ items }) {
const [count, setCount] = useState(0);
const [selected, setSelected] = useState(null);
const total = expensiveSum(items);
const handleSelect = (id) => setSelected(id);
return (
<div>
<button onClick={() => setCount(count + 1)}>Clicked {count}</button>
<p>Total: {total} / selected: {selected}</p>
<List items={items} onSelect={handleSelect} />
</div>
);
}Output for Counter. The code is verbatim; only the // ◀ comments are mine.
import { c as _c } from "react/compiler-runtime"; // ◀ added once per file; = React's useMemoCache
export function Counter(t0) {
const $ = _c(17); // ◀ 17 slots on this fiber's memo cache
const {
items
} = t0; // ◀ props destructuring moved into the body
const [count, setCount] = useState(0); // ◀ hooks stay unconditional (flattenScopesWithHooks)
const [selected, setSelected] = useState(null);
let t1;
if ($[0] !== items) { // ◀ scope 1, dep = items (sentinel on mount, so always true)
t1 = expensiveSum(items); // ◀ the "useMemo" you didn't write
$[0] = items; // ◀ store dep...
$[1] = t1; // ◀ ...and result (written together: atomic)
} else {
t1 = $[1]; // ◀ cache hit: skip the loop entirely
}
const total = t1;
let t2;
if ($[2] === Symbol.for("react.memo_cache_sentinel")) { // ◀ no reactive deps: setSelected is stable
t2 = id => setSelected(id); // ◀ the "useCallback" you didn't write; created once
$[2] = t2;
} else {
t2 = $[2];
}
const handleSelect = t2;
let t3;
if ($[3] !== count) { // ◀ inline handler closes over count → dep = count
t3 = () => setCount(count + 1);
$[3] = count;
$[4] = t3;
} else {
t3 = $[4];
}
let t4;
if ($[5] !== count || $[6] !== t3) { // ◀ <button> element, deps = count and the handler
t4 = <button onClick={t3}>Clicked {count}</button>;
$[5] = count;
$[6] = t3;
$[7] = t4;
} else {
t4 = $[7];
}
let t5;
if ($[8] !== selected || $[9] !== total) { // ◀ <p> only rebuilt when its inputs change
t5 = <p>Total: {total} / selected: {selected}</p>;
$[8] = selected;
$[9] = total;
$[10] = t5;
} else {
t5 = $[10];
}
let t6;
if ($[11] !== items) { // ◀ <List> element: handleSelect is stable, so dep = items only
t6 = <List items={items} onSelect={handleSelect} />;
$[11] = items;
$[12] = t6;
} else {
t6 = $[12]; // ◀ SAME element object as last render → see page 15
}
let t7;
if ($[13] !== t4 || $[14] !== t5 || $[15] !== t6) { // ◀ outer <div>: deps are the child elements
t7 = <div>{t4}{t5}{t6}</div>;
$[13] = t4;
$[14] = t5;
$[15] = t6;
$[16] = t7;
} else {
t7 = $[16]; // ◀ nothing changed → return the previous tree
}
return t7;
}What to take from it:
- Granularity is finer than you would write by hand. One
useMemoper JSX node, keyed on exactly what that node reads. When you click the button,countchanges, sot3,t4andt7are rebuilt, whilet5andt6(the<List>element) are reused. - Element identity is the payoff.
t6is the identical object from the previous render. That is what lets the reconciler skipListentirely; see With vs Without Compiler. - Cache writes are atomic. Inputs and outputs of a scope are written together. React relies on this property in
useMemoCache(below).
Output for List. This is also verbatim. Notice the nested scope: the map callback is cached on onSelect alone, and the mapped array on items and onSelect:
export function List(t0) {
const $ = _c(7);
const { items, onSelect } = t0;
let t1;
if ($[0] !== items || $[1] !== onSelect) {
let t2;
if ($[3] !== onSelect) {
t2 = item => <li key={item.id} onClick={() => onSelect(item.id)}>{item.name}</li>;
$[3] = onSelect;
$[4] = t2;
} else {
t2 = $[4];
}
t1 = items.map(t2);
$[0] = items; $[1] = onSelect; $[2] = t1;
} else {
t1 = $[2];
}
// ... <ul>{t1}</ul> cached on t1 (slots 5-6)
}(Simplified: I collapsed the destructuring and the last scope onto fewer lines.) When a callback captures nothing reactive, the compiler goes further and outlines it to module scope. In the page 15 example, data.map((d) => <Row .../>) becomes data.map(_temp) with function _temp(d) {...} hoisted out.33
The runtime: _c → useMemoCache
On React 19, react/compiler-runtime is a one-line re-export, export {useMemoCache as c} from './ReactHooks', and React also exposes it as React.__COMPILER_RUNTIME.34 useMemoCache is a hook in the dispatcher like any other. The mount and update dispatchers both point at the same function.35 It does not follow the usual mount/update split, and it does not allocate a Hook object in the memoizedState list (see Hooks on Mount for that list). Instead the cache lives on fiber.updateQueue.memoCache.36
// packages/react-reconciler/src/ReactFiberHooks.js (simplified)
function useMemoCache(size) {
let memoCache = currentlyRenderingFiber.updateQueue?.memoCache; // re-render in same pass
if (memoCache == null) {
const current = currentlyRenderingFiber.alternate;
const currentMemoCache = current?.updateQueue?.memoCache;
if (currentMemoCache != null) {
memoCache = {
data: enableNoCloningMemoCache
? currentMemoCache.data
: currentMemoCache.data.map(array => array.slice()), // copy-on-write
index: 0,
};
}
}
if (memoCache == null) memoCache = { data: [], index: 0 }; // first mount
// ... ensure updateQueue exists, store memoCache on it
let data = memoCache.data[memoCache.index];
if (data === undefined) {
data = memoCache.data[memoCache.index] = new Array(size);
for (let i = 0; i < size; i++) data[i] = REACT_MEMO_CACHE_SENTINEL;
}
memoCache.index++;
return data;
}The full function is in ReactFiberHooks.js.37 A few senior-level details:
- Mount: the array is filled with
Symbol.for('react.memo_cache_sentinel'). Every$[i] !== depcheck is then trivially true, because no user value equals that symbol, so every scope computes. Scopes without deps check=== sentinelexplicitly. - Update: copy-on-write.
renderWithHookssetsworkInProgress.updateQueue = nullat the start of every render.38useMemoCachetherefore clones the current fiber's arrays (slice()), unlessenableNoCloningMemoCacheis on, which it is not in v19.3.1.39 The clone means a render that is interrupted or thrown away can't corrupt the committed cache, which is the same double-buffering idea as fibers (Fiber). The cost is one array copy per compiled component per render. - Why atomic writes matter: the comment on that flag explains that because the compiler never records a scope's inputs without its outputs, a future mode could share one cache across interrupted attempts and so "resume" partway through a component.37
- Render-phase re-renders (
setStateduring render) reuse the work-in-progress cache, andresetFunctionComponentUpdateQueueresets onlyindex, not the data.40 - The slot count must be constant across renders. In DEV, React prints a console error if it changes.37
React 17/18: react-compiler-runtime
With target: '17' | '18' the import becomes react-compiler-runtime, which you install as a runtime dependency, not a dev dependency.4132 Its c uses React.__COMPILER_RUNTIME.c when present. Otherwise it falls back to a userspace polyfill: useMemo(() => new Array(size).fill($empty), []), a sentinel-filled array kept alive by an ordinary useMemo. The polyfill also sets $[$empty] = true so DevTools can recognise the array.42 The trade-off is that the cache is a regular hook slot rather than living on the update queue, and it is not copy-on-write.
Bailouts: when the compiler leaves a function alone
The compiler's stance is that skipping is always safe. By default (panicThreshold: 'none'), a function that fails validation is emitted unchanged and the build continues.4344 I fed it four known-bad components, and this is what the logger reported (examples/Bailouts.jsx, examples/Mut.jsx):
Other skip reasons: an ESLint or Flow suppression comment for React rules inside the function (findProgramSuppressions) makes the compiler assume the code breaks the rules.48 Known-incompatible library APIs, such as react-hook-form's watch and TanStack Table, are on a list (DefaultModuleTypeProvider.ts) and cause a skip.49 So do unsupported syntax such as class components, which are an explicit non-goal.50
panicThreshold controls how loud failures are. 'none' (default; the docs say production should always use it) skips silently. 'critical_errors' throws on critical or unrecognised errors. 'all_errors' fails the build on any diagnostic.4351 Config errors always throw.44 To see the skips without failing the build, pass a logger with logEvent (that's what my script does) or use the ESLint rules.
What the compiler does NOT do
- No reconciler, scheduler, lane or commit changes. It only rewrites your component bodies. Work Loop, beginWork & Reconciliation, Lanes & Scheduler and Commit Phase run the same code with or without it. The speedup comes indirectly, through stable element and prop identities hitting bailouts that already existed.
- It does not stop a component's function from running when that component has its own state update. It makes the run cheaper.
- It doesn't compile
node_modules(defaultsourcesexcludes them), class components, or non-component helpers.5250 - It doesn't share memoization between components or instances. Every fiber has its own cache.3
- It is not "perfect" memoization. Zero unnecessary recomputation is an explicit non-goal, because tracking overhead and code size would hurt startup.50
- It doesn't change semantics for code that follows the rules. Existing
useMemo/useCallbackstill work as escape hatches, for example to pin an effect dependency. The docs advise leaving existing manual memoization in place, because removing it can change the compiled output.3
See With vs Without Compiler for the fiber-by-fiber effect on a state update.
Summary
React Compiler is a build-time Babel plugin that rewrites each component and hook to memoize its own values and JSX in a per-fiber array, const $ = _c(n). It lowers your function into a control-flow-graph IR, infers what mutates together and what is reactive, groups that into reactive scopes, and emits one $[i] !== dep check per scope. At runtime it needs only useMemoCache. The reconciler, scheduler and commit phase are untouched; the compiler just hands them stable identities, and the next part shows which bailouts those hit.
Key takeaways
compilationMode:'infer'compiles only component- or hook-named functions that create JSX or call a hook;"use no memo"always wins.- HIR lowering: memoization needs data-flow facts, so the AST becomes a control-flow graph of basic blocks.
- Mutable range: a value is cached as a unit only once it is done being built, so everything that mutates it shares one memo block.
inferReactivePlaces: props, state, context and hook results are reactive; auseStatesetter is stable.flattenScopesWithHooksOrUseHIR: drops scopes containing hooks, which is whyuseStatesits outside theif ($[i] ...)blocks.- Codegen: emits
$[i] !== depchecks, or a sentinel test for dependency-free scopes, and writes a scope's inputs and outputs together. useMemoCache: keeps the cache onfiber.updateQueue.memoCache, not the hook list, and on update clones it copy-on-write (enableNoCloningMemoCacheis off).- Bailouts: by default a function that fails validation is emitted unchanged; the compiler catches only what it can prove (
items.push(1)compiled). - Not fine-grained: React still re-renders top-down from the state owner; re-renders just get cheaper.
Footnotes
-
React Compiler v1.0 (Lauren Tan, Joe Savona, Mofei Zhang, 7 Oct 2025): https://react.dev/blog/2025/10/07/react-compiler-1 (opens in a new tab) ↩ ↩2
-
react.dev, "React Compiler: Installation" (must run first in the Babel pipeline): https://react.dev/learn/react-compiler/installation (opens in a new tab) ↩ ↩2
-
react.dev, "React Compiler: Introduction": https://react.dev/learn/react-compiler/introduction (opens in a new tab) ↩ ↩2 ↩3 ↩4 ↩5
-
Compiler design doc, Architecture: compiler/docs/DESIGN_GOALS.md#L38-L55 (opens in a new tab) ↩ ↩2 ↩3 ↩4
-
BabelPluginReactCompiler— compiler/packages/babel-plugin-react-compiler/src/Babel/BabelPlugin.ts#L24-L36 (opens in a new tab) ↩ -
getReactFunctionType— compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts#L865-L911 (opens in a new tab) ↩ -
OPT_IN_DIRECTIVES/OPT_OUT_DIRECTIVES— compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts#L49-L50 (opens in a new tab) ↩ -
react.dev,
compilationModereference: https://react.dev/reference/react-compiler/compilationMode (opens in a new tab) ↩ -
runWithEnvironment→lower— compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts#L148-L158 (opens in a new tab) ↩ -
lower— compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts#L72 (opens in a new tab) ↩ -
Compiler design doc, Design Principles (HIR): compiler/docs/DESIGN_GOALS.md#L29-L36 (opens in a new tab) ↩
-
Early passes incl.
dropManualMemoization— Pipeline.ts#L160-L181 (opens in a new tab); implementation Inference/DropManualMemoization.ts#L391 (opens in a new tab) ↩ -
enterSSA— compiler/packages/babel-plugin-react-compiler/src/SSA/EnterSSA.ts#L254 (opens in a new tab); called at Pipeline.ts#L188-L198 (opens in a new tab) ↩ -
inferTypes— compiler/packages/babel-plugin-react-compiler/src/TypeInference/InferTypes.ts#L64 (opens in a new tab) ↩ -
inferTypes→validateHooksUsage— Pipeline.ts#L200-L212 (opens in a new tab) ↩ -
analyseFunctions,inferMutationAliasingEffects— Pipeline.ts#L217-L221 (opens in a new tab); implementation Inference/InferMutationAliasingEffects.ts#L98 (opens in a new tab) ↩ -
"This value cannot be modified" diagnostic — compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts#L612-L615 (opens in a new tab) ↩ ↩2
-
inferMutationAliasingRangesdoc comment — compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingRanges.ts#L32-L75 (opens in a new tab) ↩ -
Render validations after mutable ranges — Pipeline.ts#L234-L296 (opens in a new tab) ↩
-
inferReactivePlaces— compiler/packages/babel-plugin-react-compiler/src/Inference/InferReactivePlaces.ts#L205 (opens in a new tab); called at Pipeline.ts#L298-L299 (opens in a new tab) ↩ -
inferReactiveScopeVariables— compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/InferReactiveScopeVariables.ts#L60-L86 (opens in a new tab); gated onenableMemoizationat Pipeline.ts#L328-L336 (opens in a new tab) ↩ -
alignReactiveScopesToBlockScopesHIR/mergeOverlappingReactiveScopesHIR— Pipeline.ts#L384-L395 (opens in a new tab); HIR/MergeOverlappingReactiveScopesHIR.ts#L105 (opens in a new tab) ↩ -
flattenScopesWithHooksOrUseHIR— compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/FlattenScopesWithHooksOrUseHIR.ts#L20-L40 (opens in a new tab) ↩ -
propagateScopeDependenciesHIR— compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts#L53 (opens in a new tab); called at Pipeline.ts#L428-L433 (opens in a new tab) ↩ -
buildReactiveFunction— compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/BuildReactiveFunction.ts#L42 (opens in a new tab); pruning passes at Pipeline.ts#L435-L491 (opens in a new tab) ↩ -
pruneNonEscapingScopesdoc comment — compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PruneNonEscapingScopes.ts#L40-L60 (opens in a new tab) ↩ -
mergeReactiveScopesThatInvalidateTogetherdoc comment — compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/MergeReactiveScopesThatInvalidateTogether.ts#L60-L86 (opens in a new tab) ↩ -
pruneAlwaysInvalidatingScopes— compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PruneAlwaysInvalidatingScopes.ts#L18-L29 (opens in a new tab) ↩ -
codegenFunctionemitsconst $ = _c(n)— compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts#L163-L178 (opens in a new tab) ↩ -
codegenReactiveScopebuilds$[i] !== dep— CodegenReactiveFunction.ts#L560-L590 (opens in a new tab) ↩ -
Sentinel test for dependency-free scopes — CodegenReactiveFunction.ts#L650-L667 (opens in a new tab); constant
MEMO_CACHE_SENTINELat L62 (opens in a new tab) ↩ -
getReactCompilerRuntimeModule— compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts#L1337-L1345 (opens in a new tab) ↩ ↩2 -
enableFunctionOutlining→outlineFunctions— Pipeline.ts#L358-L361 (opens in a new tab) ↩ -
react/compiler-runtime→ packages/react/src/ReactCompilerRuntime.js#L10 (opens in a new tab);__COMPILER_RUNTIMEexport packages/react/src/ReactClient.js#L111 (opens in a new tab);useMemoCache→ dispatcher packages/react/src/ReactHooks.js#L212-L216 (opens in a new tab) ↩ -
HooksDispatcherOnMount/OnUpdateboth useuseMemoCache— packages/react-reconciler/src/ReactFiberHooks.js#L3926-L3981 (opens in a new tab) ↩ -
FunctionComponentUpdateQueue.memoCache— packages/react-reconciler/src/ReactFiberHooks.js#L249-L254 (opens in a new tab) ↩ -
useMemoCache— packages/react-reconciler/src/ReactFiberHooks.js#L1181-L1263 (opens in a new tab) ↩ ↩2 ↩3 -
renderWithHooksclearsupdateQueue— packages/react-reconciler/src/ReactFiberHooks.js#L529-L531 (opens in a new tab) ↩ -
enableNoCloningMemoCache = false— packages/shared/ReactFeatureFlags.js#L128 (opens in a new tab) ↩ -
resetFunctionComponentUpdateQueue— packages/react-reconciler/src/ReactFiberHooks.js#L1084-L1096 (opens in a new tab) ↩ -
react.dev,
targetreference: https://react.dev/reference/react-compiler/target (opens in a new tab) ↩ -
react-compiler-runtimecpolyfill — compiler/packages/react-compiler-runtime/src/index.ts#L19-L39 (opens in a new tab) ↩ -
react.dev,
panicThresholdreference: https://react.dev/reference/react-compiler/panicThreshold (opens in a new tab) ↩ ↩2 -
handleError(panicThreshold) — compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts#L246-L262 (opens in a new tab) ↩ ↩2 -
validateHooksUsage— compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateHooksUsage.ts#L86 (opens in a new tab) ↩ -
validateNoRefAccessInRender— compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccessInRender.ts#L122 (opens in a new tab) ↩ -
react.dev, "Debugging and Troubleshooting": https://react.dev/learn/react-compiler/debugging (opens in a new tab) ↩
-
findProgramSuppressionsusage — compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts#L431-L443 (opens in a new tab) ↩ -
react.dev,
incompatible-librarylint: https://react.dev/reference/eslint-plugin-react-hooks/lints/incompatible-library (opens in a new tab); known list incl.react-hook-form,@tanstack/react-tablein compiler/packages/babel-plugin-react-compiler/src/HIR/DefaultModuleTypeProvider.ts#L46-L89 (opens in a new tab) ↩ -
Compiler design doc, Goals / Non-Goals: compiler/docs/DESIGN_GOALS.md#L5-L27 (opens in a new tab) ↩ ↩2 ↩3
-
PanicThresholdOptionsSchema— compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts#L20-L37 (opens in a new tab) ↩ -
defaultOptions(compilationMode: 'infer',panicThreshold: 'none',sourcesexcludesnode_modules,target: '19') — compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts#L312-L330 (opens in a new tab) ↩