Reactive Scopes
Understanding Reactive Scopes
Reactive Scopes are the fundamental units of memoization created by the React Compiler. Unlike manual memoization where you use useMemo or useCallback for individual values, the compiler automatically groups instructions into scopes to ensure that related data is cached and invalidated efficiently.
A reactive scope represents a block of code that produces one or more "reactive" values. When the dependencies (inputs) of a scope change, the compiler re-executes the code within that scope to produce fresh values.
How Scopes are Formed
The compiler identifies reactive scopes by analyzing the lifecycle of variables—specifically where they are declared, mutated, and consumed.
Grouping and Interleaving
If multiple variables are mutated together or interleave their logic, the compiler groups them into a single reactive scope. This ensures that the internal state of these objects remains consistent.
function Component(props) {
// These two objects are mutated together,
// so they will likely share a reactive scope.
const items = [];
const metadata = {};
metadata.count = props.list.length;
items.push(...props.list);
return <ItemList items={items} info={metadata} />;
}
The "Escape" Rule
To keep the generated code lean, the compiler does not memoize every single variable. It uses "Escape Analysis" to determine if a value needs a reactive scope. A value is considered to "escape" (and thus requires memoization) if:
- It is returned by the component: Directly or as part of another object/array.
- It is passed to a hook: Values passed to hooks (like
useEffector custom hooks) might be stored by React or an external store. - It is used as a JSX prop: Since JSX elements are often passed to other components, their props must be stable to prevent unnecessary re-renders.
Example: Escaping vs. Non-Escaping
In the following example, the compiler optimizes by only memoizing what is necessary:
function MyComponent(props) {
// a does not escape and is not a dependency of an escaping value.
// The compiler may prune its scope to reduce code size.
const a = { value: props.id };
// b is returned (it escapes), so it is wrapped in a reactive scope.
const b = [props.name];
return b;
}
Dependency Tracking and Transitive Memoization
A critical feature of reactive scopes is the preservation of the "memoization chain." If an escaping value (Value B) depends on a non-escaping value (Value A), the compiler will memoize Value A even if it doesn't escape.
Failing to memoize Value A would cause Value B's scope to invalidate on every render, breaking the performance benefits for anything consuming Value B.
function Component(props) {
// 'a' doesn't escape the component...
const a = [props.a];
// ...but 'b' (which is returned) depends on 'a'.
// The compiler ensures 'a' is memoized so that 'b' stays stable.
const b = {};
b.linkedData = a;
b.other = props.b;
return b;
}
Rules for Clean Scopes
To help the compiler identify and optimize reactive scopes effectively, follow these patterns:
Avoid Capitalized Function Calls
Capitalized names are reserved for React Components. Components should always be rendered using JSX syntax (<MyComponent />), not called as regular functions. Calling a component directly can confuse the compiler’s ability to track reactive boundaries.
// ❌ Avoid calling capitalized functions
const data = SomeComponent();
// ✅ Use JSX or rename if it's a regular utility
const data = someUtility();
Keep Mutations Local
The compiler is most effective when mutations are contained within the same conceptual block where the variable is defined. Global mutations or mutating props directly can prevent the compiler from safely creating a reactive scope, leading to unoptimized code.
Validation and Pruning
The compiler performs a final pass called Pruning. During this stage, it removes any reactive scopes that were initially created but later found to be unnecessary (e.g., a scope whose output never escapes and doesn't affect the UI). This ensures that the final bundle contains only the memoization logic that actually provides a performance benefit.