Document Version: 1.0
Date: 7/27/25
Issue Reference: React Issue #34014
Research Tool: octocode-mcp
The React Compiler exhibits conservative behavior when optimizing function calls returned from custom hooks, leading to missed memoization opportunities. Functions that are referentially stable and return deterministic values are not automatically memoized, requiring manual useMemo wrapping to achieve optimization. This document provides a comprehensive technical analysis of the root cause, current implementation details, and potential solutions.
When using the React Compiler (version 0.0.0-experimental-2db0664-20250725), functions returned from custom hooks are not automatically memoized, even when they are referentially stable and return deterministic values.
Original Code:
function getIdStore() {
  let id = 0;
  return function getUniqueId() {
    return `id-${++id}`;
  };
}
const getUniqueId = getIdStore();
function useId() {
  return getUniqueId();
}
function useIdWithUseMemo() {
  return React.useMemo(() => getUniqueId(), []);
}
function useIdWithUseState() {
  const [id] = React.useState(getUniqueId())
  return id;
}Compiled Output:
// useId() - NOT optimized
function useId() {
  return getUniqueId();
}
// useIdWithUseMemo() - IS optimized
function useIdWithUseMemo() {
  const $ = _c(1);
  let t0;
  if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
    t0 = getUniqueId();
    $[0] = t0;
  } else {
    t0 = $[0];
  }
  return t0;
}The React Compiler processes code through several phases, each implemented in distinct modules:
compiler/packages/babel-plugin-react-compiler/src/
├── Entrypoint/Pipeline.ts          # Main compilation pipeline
├── HIR/                           # High-level Intermediate Representation
├── Inference/                     # Type and effect inference
├── Optimization/                  # Code optimization passes
│   └── OutlineFunctions.ts       # Function hoisting logic
├── ReactiveScopes/               # Memoization and reactive scope management
│   ├── BuildReactiveFunction.ts  # Converts HIR to reactive representation
│   └── CodegenReactiveFunction.ts # Generates final optimized code
└── Validation/                   # Code validation and safety checks
File: compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts
The compiler's function outlining logic determines when functions should be hoisted to module level rather than memoized:
// Lines 25-31
if (
  value.kind === 'FunctionExpression' &&
  value.loweredFunc.func.context.length === 0 &&
  // TODO: handle outlining named functions
  value.loweredFunc.func.id === null &&
  !fbtOperands.has(lvalue.identifier.id)
) {
  // Function gets hoisted to _temp
}Hoisting Criteria:
- Function expressions with no closure dependencies
 - Anonymous functions
 - Non-FBT operands
 
This explains the _temp function generation in the issue's compiled output.
File: compiler/packages/babel-plugin-react-compiler/src/Inference/DropManualMemoization.ts
The compiler only recognizes explicit memoization hints:
// Lines 34-36
type ManualMemoCallee = {
  kind: 'useMemo' | 'useCallback';
  loadInstr: TInstruction<LoadGlobal> | TInstruction<PropertyLoad>;
};Manual Memoization Detection:
- Looks for explicit 
useMemo/useCallbackcalls - Preserves developer-specified memoization boundaries
 - Does not infer memoization opportunities automatically
 
File: compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts
The code generation phase handles memoization through reactive scopes:
// Lines 58-59
export const MEMO_CACHE_SENTINEL = 'react.memo_cache_sentinel';
export const EARLY_RETURN_SENTINEL = 'react.early_return_sentinel';Memoization Mechanics:
- Uses 
useMemoCacheruntime function - Implements sentinel-based cache invalidation
 - Generates cache slot management code
 
- 
HIR Generation (
BuildHIR.ts)- Converts AST to intermediate representation
 - Identifies function expressions and calls
 
 - 
Function Analysis (
AnalyseFunctions.ts)- Determines function purity and side effects
 - Limitation: Conservative analysis, assumes impurity by default
 
 - 
Outlining Pass (
OutlineFunctions.ts)- Hoists closure-free functions
 - Issue: Functions get hoisted instead of their calls being memoized
 
 - 
Reactive Scope Building (
BuildReactiveFunction.ts)- Creates memoization boundaries
 - Gap: No automatic inference for function call results
 
 
File: compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts
The compiler's behavior is controlled by configuration options:
// Lines 189-205
/**
 * Enable using information from existing useMemo/useCallback to understand when a value is done
 * being mutated. With this mode enabled, Forget will still discard the actual useMemo/useCallback
 * calls and may memoize slightly differently. However, it will assume that the values produced
 * are not subsequently modified, guaranteeing that the value will be memoized.
 */
enablePreserveExistingManualUseMemo: z.boolean().default(false),Key Configuration Flags:
enablePreserveExistingManualUseMemo: Preserves manual memoization hintsenableResetCacheOnSourceFileChanges: HMR supportcustomHooks: Custom hook type definitions
function useId() {
  return getUniqueId(); // NOT memoized
}Reason: Function call not recognized as memoization candidate
function useIdWithUseMemo() {
  return React.useMemo(() => getUniqueId(), []);
}Reason: Explicit useMemo provides memoization hint to compiler
function useIdWithUseState() {
  const [id] = React.useState(getUniqueId())
  return id;
}Reason: useState initialization is recognized as requiring memoization
File: compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hoisting-simple-function-expression.expect.md
The compiler's hoisting behavior is demonstrated in test cases:
// Input
function hoisting() {
  const foo = () => bar();
  const bar = () => 1;
  return foo();
}
// Compiled Output
function hoisting() {
  const $ = _c(1);
  let t0;
  if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
    const foo = () => bar();
    const bar = _temp; // Hoisted function
    t0 = foo(); // Call result is memoized
    $[0] = t0;
  } else {
    t0 = $[0];
  }
  return t0;
}
function _temp() {
  return 1;
}Key Observation: The function bar gets hoisted to _temp, but the call result foo() gets memoized.
- Missed Optimizations: Stable function call results not cached
 - Unnecessary Re-computations: Functions called on every render
 - Memory Efficiency: No impact on memory usage patterns
 
- Manual Intervention Required: Developers must wrap obvious cases
 - Inconsistent Behavior: Some patterns optimized, others not
 - Learning Curve: Understanding when manual memoization is needed
 
- Reduced Compiler Benefits: Less automatic optimization than expected
 - Migration Effort: Existing codebases may need manual 
useMemoadditions - Trust Issues: Uncertainty about what gets optimized
 
- 
Side Effect Detection
- Cannot reliably determine function purity
 - Conservative approach to prevent incorrect optimizations
 
 - 
Context Dependency Analysis
- Complex closure analysis required
 - Inter-procedural analysis limitations
 
 - 
Type System Integration
- Limited integration with TypeScript type information
 - Cannot leverage pure function annotations
 
 
- 
Stale Closure Prevention
- Risk of capturing outdated variable references
 - Complex dependency tracking required
 
 - 
Hook Rules Compliance
- Memoization must not violate React Hook rules
 - Conditional memoization complexity
 
 
// Proposed: Recognize stable custom hook patterns
function useStableValue<T>(fn: () => T, deps: unknown[] = []): T {
  // Compiler could auto-optimize this pattern
}// Proposed: JSDoc annotations for pure functions
/** @pure */
function getUniqueId() {
  return `id-${Math.random()}`;
}// Environment.ts enhancement
customPureFunctions: z.array(z.string()).default([]),
enableAggressiveMemoization: z.boolean().default(false),- Inter-procedural purity analysis
 - Effect system integration
 - Dependency graph optimization
 
- Dynamic purity detection
 - Adaptive memoization strategies
 - Performance-guided optimization
 
- Pure function type annotations
 - Effect type tracking
 - Compiler directive support
 
- 
Immediate Actions
- Document current limitations clearly
 - Provide migration guide for common patterns
 - Add configuration options for aggressive memoization
 
 - 
Medium-term Improvements
- Enhance function call analysis
 - Add custom hook pattern recognition
 - Improve developer debugging tools
 
 - 
Long-term Vision
- Develop comprehensive effect system
 - Integrate with TypeScript for better analysis
 - Create ecosystem of optimization hints
 
 
- 
Current Workarounds
- Use explicit 
useMemofor stable function calls - Mark pure functions with consistent patterns
 - Test compiler output to verify optimizations
 
 - Use explicit 
 - 
Best Practices
- Prefer 
useStatefor initialization when possible - Keep custom hooks simple and focused
 - Document expected compiler behavior
 
 - Prefer 
 
The React Compiler's conservative approach to function call memoization represents a deliberate trade-off between safety and optimization aggressiveness. While this limits automatic optimization opportunities, it ensures correctness and predictable behavior. The issue can be addressed through a combination of enhanced static analysis, developer annotations, and configuration options.
The current workaround of explicit useMemo wrapping is functional but reduces the compiler's value proposition. Future improvements should focus on bridging this gap while maintaining the compiler's safety guarantees.
- React Issue #34014
 - React Compiler Source Code
 - React Compiler Documentation
 - Pipeline Implementation: 
compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts - Function Outlining: 
compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts - Manual Memoization Detection: 
compiler/packages/babel-plugin-react-compiler/src/Inference/DropManualMemoization.ts - Code Generation: 
compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts - Environment Configuration: 
compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts 
Document Prepared By: AI Analysis of React Compiler Source Code using octocode-mcp
Last Updated: 7/27/25
Status: Analysis Complete