From 24613ba9da29fbe1783b486d41c6eedcb09d0442 Mon Sep 17 00:00:00 2001 From: Raashish Aggarwal <94279692+raashish1601@users.noreply.github.com> Date: Sat, 2 May 2026 11:07:49 +0530 Subject: [PATCH] Preserve module global snapshots in effects --- .../src/Optimization/ConstantPropagation.ts | 8 ++ ...copy-before-assignment-in-effect.expect.md | 74 +++++++++++++++++++ ...global-copy-before-assignment-in-effect.js | 21 ++++++ 3 files changed, 103 insertions(+) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/global-copy-before-assignment-in-effect.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/global-copy-before-assignment-in-effect.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/ConstantPropagation.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/ConstantPropagation.ts index 52a5a8b8c769..ce65787940b9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Optimization/ConstantPropagation.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/ConstantPropagation.ts @@ -231,6 +231,14 @@ function evaluateInstruction( return value; } case 'LoadGlobal': { + /* + * Module-local bindings may be reassigned by code outside the current + * function's reactive graph. A load from one of those bindings is a + * snapshot, not a constant reference that can replace later local reads. + */ + if (value.binding.kind === 'ModuleLocal') { + return null; + } return value; } case 'ComputedLoad': { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/global-copy-before-assignment-in-effect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/global-copy-before-assignment-in-effect.expect.md new file mode 100644 index 000000000000..defb550d8219 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/global-copy-before-assignment-in-effect.expect.md @@ -0,0 +1,74 @@ + +## Input + +```javascript +import {useEffect} from 'react'; + +let i = 0; +const log = []; + +function Component() { + useEffect(() => { + const runNumber = i; + log.push(`effect ${runNumber}`); + i += 1; + return () => { + log.push(`cleanup ${runNumber}`); + }; + }, []); + return
OK
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { useEffect } from "react"; + +let i = 0; +const log = []; + +function Component() { + const $ = _c(2); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = []; + $[0] = t0; + } else { + t0 = $[0]; + } + useEffect(_temp, t0); + let t1; + if ($[1] === Symbol.for("react.memo_cache_sentinel")) { + t1 =
OK
; + $[1] = t1; + } else { + t1 = $[1]; + } + return t1; +} +function _temp() { + const runNumber = i; + log.push(`effect ${runNumber}`); + i = i + 1; + return () => { + log.push(`cleanup ${runNumber}`); + }; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{}], +}; + +``` + +### Eval output +(kind: ok)
OK
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/global-copy-before-assignment-in-effect.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/global-copy-before-assignment-in-effect.js new file mode 100644 index 000000000000..f2ebc1a0f4b1 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/global-copy-before-assignment-in-effect.js @@ -0,0 +1,21 @@ +import {useEffect} from 'react'; + +let i = 0; +const log = []; + +function Component() { + useEffect(() => { + const runNumber = i; + log.push(`effect ${runNumber}`); + i += 1; + return () => { + log.push(`cleanup ${runNumber}`); + }; + }, []); + return
OK
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{}], +};