From 395dc9687a708ae37d5756f0bb92bba2e89c9e5c Mon Sep 17 00:00:00 2001 From: "Saurabh.Chopade" Date: Tue, 17 Feb 2026 00:19:49 +0530 Subject: [PATCH] eslint-plugin-react-hooks: allow ref-guarded setState in effects --- .../src/shared/RunReactCompiler.ts | 2 + .../ReactCompilerRuleTypescript-test.ts | 55 +++++++++++++++++++ .../src/shared/RunReactCompiler.ts | 2 + 3 files changed, 59 insertions(+) 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', },