From 98cec2ec262ee5eb32ffb39d694bec64f0c05732 Mon Sep 17 00:00:00 2001
From: Srinu desetti <159899608+webdevelopersrinu@users.noreply.github.com>
Date: Wed, 16 Sep 2026 14:23:44 +0530
Subject: [PATCH] [compiler] Add regression test for useEffectEvent reading a
variable declared later (#37618)
Fixes #37209
The stale value from the issue does not reproduce on main anymore.
The compiler leaves the useEffectEvent callback unmemoized, so it
always reads the latest value. I added the repro from the issue as
a test fixture so this stays covered.
The new fixture passes with yarn snap.
---
...-capture-variable-declared-later.expect.md | 101 ++++++++++++++++++
...ctEvent-capture-variable-declared-later.js | 29 +++++
2 files changed, 130 insertions(+)
create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useEffectEvent-capture-variable-declared-later.expect.md
create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useEffectEvent-capture-variable-declared-later.js
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useEffectEvent-capture-variable-declared-later.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useEffectEvent-capture-variable-declared-later.expect.md
new file mode 100644
index 0000000000..ed9df25f8d
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useEffectEvent-capture-variable-declared-later.expect.md
@@ -0,0 +1,101 @@
+
+## Input
+
+```javascript
+// @compilationMode:"infer"
+import {useEffect, useEffectEvent, useState} from 'react';
+
+// Repro from https://github.com/facebook/react/issues/37209
+// The effect event reads `length`, which is declared after the callback.
+// The callback must not be memoized, so it always sees the latest value.
+export default function App() {
+ const [data, setData] = useState([]);
+
+ const logLength = useEffectEvent(() => {
+ console.log('length', length);
+ });
+
+ const length = data.length - 0;
+
+ useEffect(() => {
+ setTimeout(() => setData([1, 2]), 300);
+
+ addEventListener('click', logLength);
+ return () => removeEventListener('click', logLength);
+ }, []);
+
+ return
Click anywhere to log the length
;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: App,
+ params: [{}],
+};
+
+```
+
+## Code
+
+```javascript
+import { c as _c } from "react/compiler-runtime"; // @compilationMode:"infer"
+import { useEffect, useEffectEvent, useState } from "react";
+
+// Repro from https://github.com/facebook/react/issues/37209
+// The effect event reads `length`, which is declared after the callback.
+// The callback must not be memoized, so it always sees the latest value.
+export default function App() {
+ const $ = _c(5);
+ let t0;
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
+ t0 = [];
+ $[0] = t0;
+ } else {
+ t0 = $[0];
+ }
+ const [data, setData] = useState(t0);
+
+ const logLength = useEffectEvent(() => {
+ console.log("length", length);
+ });
+
+ const length = data.length - 0;
+ let t1;
+ if ($[1] !== logLength) {
+ t1 = () => {
+ setTimeout(() => setData([1, 2]), 300);
+
+ addEventListener("click", logLength);
+ return () => removeEventListener("click", logLength);
+ };
+ $[1] = logLength;
+ $[2] = t1;
+ } else {
+ t1 = $[2];
+ }
+ let t2;
+ if ($[3] === Symbol.for("react.memo_cache_sentinel")) {
+ t2 = [];
+ $[3] = t2;
+ } else {
+ t2 = $[3];
+ }
+ useEffect(t1, t2);
+ let t3;
+ if ($[4] === Symbol.for("react.memo_cache_sentinel")) {
+ t3 = Click anywhere to log the length
;
+ $[4] = t3;
+ } else {
+ t3 = $[4];
+ }
+ return t3;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: App,
+ params: [{}],
+};
+
+```
+
+### Eval output
+(kind: exception) (0 , _react.useEffectEvent) is not a function
\ No newline at end of file
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useEffectEvent-capture-variable-declared-later.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useEffectEvent-capture-variable-declared-later.js
new file mode 100644
index 0000000000..0f1c90fb33
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useEffectEvent-capture-variable-declared-later.js
@@ -0,0 +1,29 @@
+// @compilationMode:"infer"
+import {useEffect, useEffectEvent, useState} from 'react';
+
+// Repro from https://github.com/facebook/react/issues/37209
+// The effect event reads `length`, which is declared after the callback.
+// The callback must not be memoized, so it always sees the latest value.
+export default function App() {
+ const [data, setData] = useState([]);
+
+ const logLength = useEffectEvent(() => {
+ console.log('length', length);
+ });
+
+ const length = data.length - 0;
+
+ useEffect(() => {
+ setTimeout(() => setData([1, 2]), 300);
+
+ addEventListener('click', logLength);
+ return () => removeEventListener('click', logLength);
+ }, []);
+
+ return Click anywhere to log the length
;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: App,
+ params: [{}],
+};