Skip to content

Commit a2bab4e

Browse files
committed
[compiler] Fix false positive for local mutation in filter callbacks
1 parent 5682442 commit a2bab4e

File tree

2 files changed

+71
-0
lines changed

2 files changed

+71
-0
lines changed

compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateLocalsNotReassignedAfterRender.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,14 @@ function getContextReassignment(
161161
}
162162
break;
163163
}
164+
case 'PropertyLoad': {
165+
/*
166+
* Property loads should not inherit reassignment from their receiver.
167+
* A value like `someItems.length` is not itself a reassigning function,
168+
* even if `someItems` originated from a callback that mutates context.
169+
*/
170+
break;
171+
}
164172
default: {
165173
let operands = eachInstructionValueOperand(value);
166174
// If we're calling a function that doesn't let its arguments escape, only test the callee

packages/eslint-plugin-react-hooks/__tests__/ReactCompilerRuleTypescript-test.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,69 @@ const tests: CompilerTestCases = {
3636
}
3737
`,
3838
},
39+
{
40+
name: 'Local mutation within filter callback is allowed during render',
41+
filename: 'test.tsx',
42+
code: normalizeIndent`
43+
import React from 'react';
44+
function Component({items}) {
45+
const stuff = React.useMemo(() => {
46+
let isCool = false;
47+
const someItems = items.filter(cause => {
48+
if (cause.foo) {
49+
isCool = true;
50+
}
51+
return true;
52+
});
53+
if (someItems.length > 0) {
54+
return {someItems, isCool};
55+
}
56+
}, [items]);
57+
return <div>{stuff?.isCool ? 'cool' : 'not cool'}</div>;
58+
}
59+
`,
60+
},
61+
{
62+
name: 'Local mutation within map callback is allowed during render',
63+
filename: 'test.tsx',
64+
code: normalizeIndent`
65+
import React from 'react';
66+
function Component({items}) {
67+
const stuff = React.useMemo(() => {
68+
let isCool = false;
69+
const mappedItems = items.map(cause => {
70+
if (cause.foo) {
71+
isCool = true;
72+
}
73+
return cause;
74+
});
75+
if (mappedItems.length > 0) {
76+
return {mappedItems, isCool};
77+
}
78+
}, [items]);
79+
return <div>{stuff?.isCool ? 'cool' : 'not cool'}</div>;
80+
}
81+
`,
82+
},
83+
{
84+
name: 'Local mutation within forEach callback is allowed during render',
85+
filename: 'test.tsx',
86+
code: normalizeIndent`
87+
import React from 'react';
88+
function Component({items}) {
89+
const stuff = React.useMemo(() => {
90+
let isCool = false;
91+
items.forEach(cause => {
92+
if (cause.foo) {
93+
isCool = true;
94+
}
95+
});
96+
return {isCool};
97+
}, [items]);
98+
return <div>{stuff.isCool ? 'cool' : 'not cool'}</div>;
99+
}
100+
`,
101+
},
39102
{
40103
name: 'Repro for hooks as normal values',
41104
filename: 'test.tsx',

0 commit comments

Comments
 (0)