[compiler] Fix false positive for useMemo reassigning context vars (#34904)
Within a function expression local variables may use StoreContext for local context variables, so the reassignment check here was firing too often. We should only report an error for variables that are declared outside the function, ie part of its `context`. --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34904). * #34903 * __->__ #34904
Joseph Savona committed
Oct 20, 2025 at 08:42 UTC
2bcbf254f168ddec567156f802d19315e64e4aa8
3 files changed
+74
-14
compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateUseMemo.ts
+17
-14
@@ -184,25 +184,28 @@ function validateNoContextVariableAssignment(
184
fn: HIRFunction,
185
errors: CompilerError,
186
): void {
187
+ const context = new Set(fn.context.map(place => place.identifier.id));
188
for (const block of fn.body.blocks.values()) {
189
for (const instr of block.instructions) {
190
const value = instr.value;
191
switch (value.kind) {
192
case 'StoreContext': {
192
- errors.pushDiagnostic(
193
- CompilerDiagnostic.create({
194
- category: ErrorCategory.UseMemo,
195
- reason:
196
- 'useMemo() callbacks may not reassign variables declared outside of the callback',
197
- description:
198
- 'useMemo() callbacks must be pure functions and cannot reassign variables defined outside of the callback function',
199
- suggestions: null,
200
- }).withDetails({
201
- kind: 'error',
202
- loc: value.lvalue.place.loc,
203
- message: 'Cannot reassign variable',
204
- }),
205
- );
193
+ if (context.has(value.lvalue.place.identifier.id)) {
194
+ errors.pushDiagnostic(
195
+ CompilerDiagnostic.create({
196
+ category: ErrorCategory.UseMemo,
197
+ reason:
198
+ 'useMemo() callbacks may not reassign variables declared outside of the callback',
199
+ description:
200
+ 'useMemo() callbacks must be pure functions and cannot reassign variables defined outside of the callback function',
201
+ suggestions: null,
202
+ }).withDetails({
203
+ kind: 'error',
204
+ loc: value.lvalue.place.loc,
205
+ message: 'Cannot reassign variable',
206
+ }),
207
+ );
208
+ }
209
break;
210
}
211
}
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassign-variable-in-usememo.expect.md
new
+45
@@ -0,0 +1,45 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+// @flow
6
+export hook useItemLanguage(items) {
7
+ return useMemo(() => {
8
+ let language: ?string = null;
9
+ items.forEach(item => {
10
+ if (item.language != null) {
11
+ language = item.language;
12
+ }
13
+ });
14
+ return language;
15
+ }, [items]);
16
+}
17
+
18
+```
19
+
20
+## Code
21
+
22
+```javascript
23
+import { c as _c } from "react/compiler-runtime";
24
+export function useItemLanguage(items) {
25
+ const $ = _c(2);
26
+ let language;
27
+ if ($[0] !== items) {
28
+ language = null;
29
+ items.forEach((item) => {
30
+ if (item.language != null) {
31
+ language = item.language;
32
+ }
33
+ });
34
+ $[0] = items;
35
+ $[1] = language;
36
+ } else {
37
+ language = $[1];
38
+ }
39
+ return language;
40
+}
41
+
42
+```
43
+
44
+### Eval output
45
+(kind: exception) Fixture not implemented
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassign-variable-in-usememo.js
new
+12
@@ -0,0 +1,12 @@
1
+// @flow
2
+export hook useItemLanguage(items) {
3
+ return useMemo(() => {
4
+ let language: ?string = null;
5
+ items.forEach(item => {
6
+ if (item.language != null) {
7
+ language = item.language;
8
+ }
9
+ });
10
+ return language;
11
+ }, [items]);
12
+}