Skip to content

Commit 395dc96

Browse files
eslint-plugin-react-hooks: allow ref-guarded setState in effects
1 parent 47d1ad1 commit 395dc96

3 files changed

Lines changed: 59 additions & 0 deletions

File tree

compiler/packages/eslint-plugin-react-compiler/src/shared/RunReactCompiler.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ const COMPILER_OPTIONS: PluginOptions = {
3838
validateNoCapitalizedCalls: [],
3939
validateHooksUsage: true,
4040
validateNoDerivedComputationsInEffects: true,
41+
// Keep this explicit so eslint behavior does not depend on compiler defaults.
42+
enableAllowSetStateFromRefsInEffects: true,
4143
}),
4244
};
4345

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

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,62 @@ const tests: CompilerTestCases = {
195195
],
196196
};
197197

198+
const setStateInEffectTests: CompilerTestCases = {
199+
valid: [
200+
{
201+
name: 'Allows setState in ref-guarded first render branch',
202+
filename: 'test.tsx',
203+
code: normalizeIndent`
204+
import {useEffect, useRef, useState} from 'react';
205+
206+
function App() {
207+
const isFirstRender = useRef(true);
208+
const [stateValue, setStateValue] = useState(false);
209+
210+
useEffect(() => {
211+
if (isFirstRender.current == true) {
212+
isFirstRender.current = false;
213+
setStateValue(true);
214+
}
215+
}, []);
216+
217+
return stateValue ? null : null;
218+
}
219+
`,
220+
},
221+
],
222+
invalid: [
223+
{
224+
name: 'Still reports synchronous setState in effect body',
225+
filename: 'test.tsx',
226+
code: normalizeIndent`
227+
import {useEffect, useState} from 'react';
228+
229+
function App() {
230+
const [stateValue, setStateValue] = useState(false);
231+
232+
useEffect(() => {
233+
setStateValue(true);
234+
}, []);
235+
236+
return stateValue ? null : null;
237+
}
238+
`,
239+
errors: [
240+
{
241+
message: /Calling setState synchronously within an effect/,
242+
},
243+
],
244+
},
245+
],
246+
};
247+
198248
const eslintTester = new ESLintTesterV8({
199249
parser: require.resolve('@typescript-eslint/parser-v5'),
200250
});
201251
eslintTester.run('react-compiler', allRules['immutability'].rule, tests);
252+
eslintTester.run(
253+
'react-compiler set-state-in-effect',
254+
allRules['set-state-in-effect'].rule,
255+
setStateInEffectTests,
256+
);

packages/eslint-plugin-react-hooks/src/shared/RunReactCompiler.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,8 @@ const COMPILER_OPTIONS: PluginOptions = {
138138
validateNoDerivedComputationsInEffects: true,
139139
// Temporarily enabled for internal testing
140140
enableUseKeyedState: true,
141+
// Keep this explicit so eslint behavior does not depend on compiler defaults.
142+
enableAllowSetStateFromRefsInEffects: true,
141143
enableVerboseNoSetStateInEffect: true,
142144
validateExhaustiveEffectDependencies: 'extra-only',
143145
},

0 commit comments

Comments
 (0)