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: [{}],
+};