diff --git a/compiler/packages/eslint-plugin-react-compiler/src/shared/RunReactCompiler.ts b/compiler/packages/eslint-plugin-react-compiler/src/shared/RunReactCompiler.ts index aa55c64237e1..6afd18bdc5af 100644 --- a/compiler/packages/eslint-plugin-react-compiler/src/shared/RunReactCompiler.ts +++ b/compiler/packages/eslint-plugin-react-compiler/src/shared/RunReactCompiler.ts @@ -38,6 +38,8 @@ const COMPILER_OPTIONS: PluginOptions = { validateNoCapitalizedCalls: [], validateHooksUsage: true, validateNoDerivedComputationsInEffects: true, + // Keep this explicit so eslint behavior does not depend on compiler defaults. + enableAllowSetStateFromRefsInEffects: true, }), }; diff --git a/packages/eslint-plugin-react-hooks/__tests__/ReactCompilerRuleTypescript-test.ts b/packages/eslint-plugin-react-hooks/__tests__/ReactCompilerRuleTypescript-test.ts index a0d0f6bdbc8e..6429d91c11e7 100644 --- a/packages/eslint-plugin-react-hooks/__tests__/ReactCompilerRuleTypescript-test.ts +++ b/packages/eslint-plugin-react-hooks/__tests__/ReactCompilerRuleTypescript-test.ts @@ -195,7 +195,62 @@ const tests: CompilerTestCases = { ], }; +const setStateInEffectTests: CompilerTestCases = { + valid: [ + { + name: 'Allows setState in ref-guarded first render branch', + filename: 'test.tsx', + code: normalizeIndent` + import {useEffect, useRef, useState} from 'react'; + + function App() { + const isFirstRender = useRef(true); + const [stateValue, setStateValue] = useState(false); + + useEffect(() => { + if (isFirstRender.current == true) { + isFirstRender.current = false; + setStateValue(true); + } + }, []); + + return stateValue ? null : null; + } + `, + }, + ], + invalid: [ + { + name: 'Still reports synchronous setState in effect body', + filename: 'test.tsx', + code: normalizeIndent` + import {useEffect, useState} from 'react'; + + function App() { + const [stateValue, setStateValue] = useState(false); + + useEffect(() => { + setStateValue(true); + }, []); + + return stateValue ? null : null; + } + `, + errors: [ + { + message: /Calling setState synchronously within an effect/, + }, + ], + }, + ], +}; + const eslintTester = new ESLintTesterV8({ parser: require.resolve('@typescript-eslint/parser-v5'), }); eslintTester.run('react-compiler', allRules['immutability'].rule, tests); +eslintTester.run( + 'react-compiler set-state-in-effect', + allRules['set-state-in-effect'].rule, + setStateInEffectTests, +); diff --git a/packages/eslint-plugin-react-hooks/src/shared/RunReactCompiler.ts b/packages/eslint-plugin-react-hooks/src/shared/RunReactCompiler.ts index 9aaddb07e656..bfc11b13b065 100644 --- a/packages/eslint-plugin-react-hooks/src/shared/RunReactCompiler.ts +++ b/packages/eslint-plugin-react-hooks/src/shared/RunReactCompiler.ts @@ -138,6 +138,8 @@ const COMPILER_OPTIONS: PluginOptions = { validateNoDerivedComputationsInEffects: true, // Temporarily enabled for internal testing enableUseKeyedState: true, + // Keep this explicit so eslint behavior does not depend on compiler defaults. + enableAllowSetStateFromRefsInEffects: true, enableVerboseNoSetStateInEffect: true, validateExhaustiveEffectDependencies: 'extra-only', },