Skip to content

fix: detect setState in useEffect for anonymous component callbacks passed to HOFs#35934

Open
fresh3nough wants to merge 1 commit intofacebook:mainfrom
fresh3nough:fix/set-state-in-effect-hof-35910
Open

fix: detect setState in useEffect for anonymous component callbacks passed to HOFs#35934
fresh3nough wants to merge 1 commit intofacebook:mainfrom
fresh3nough:fix/set-state-in-effect-hof-35910

Conversation

@fresh3nough
Copy link

Summary

Fixes #35910

The react-hooks/set-state-in-effect ESLint rule failed to detect setState calls inside useEffect when the component was an anonymous callback passed to a higher-order function (e.g. const WrappedComponent = wrap(() => { ... })).

Root Cause

Two issues prevented detection:

  1. getFunctionName in Program.ts did not look through CallExpression parents. For const Foo = wrap(() => { ... }), the arrow function's parent is the CallExpression, not the VariableDeclarator, so getFunctionName returned null and the compiler never recognized the anonymous function as a component.

  2. mayContainReactCode heuristic in RunReactCompiler.ts only matched VariableDeclaration inits that were directly ArrowFunctionExpression or FunctionExpression. Files containing only HOF-wrapped components (e.g. const Foo = wrap(() => { ... })) would be skipped entirely.

Changes

  • compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts: Extended getFunctionName to look through CallExpression parents to the VariableDeclarator grandparent, so const Foo = wrap(() => { ... }) correctly gives the inner arrow function the name Foo.

  • packages/eslint-plugin-react-hooks/src/shared/RunReactCompiler.ts: Extended the mayContainReactCode heuristic to also match CallExpression inits in variable declarations with component/hook-like names.

  • packages/eslint-plugin-react-hooks/__tests__/ReactCompilerRuleTypescript-test.ts: Added test cases for both the set-state-in-effect and immutability rules to cover HOF-wrapped component detection.

Steps to Reproduce

import { useEffect, useState } from 'react';

const wrap = (value) => value;

// Direct component - setState detected correctly
export function DirectComponent() {
  const [value, setValue] = useState(0);
  useEffect(() => {
    setValue(1); // detected
  }, []);
  return <div>{value}</div>;
}

// HOF-wrapped component - setState was NOT detected (now fixed)
export const WrappedComponent = wrap(() => {
  const [value, setValue] = useState(0);
  useEffect(() => {
    setValue(1); // was missed, now detected
  }, []);
  return <div>{value}</div>;
});

Test Plan

All 5086 eslint-plugin-react-hooks tests pass, including 2 new test cases.

@meta-cla meta-cla bot added the CLA Signed label Mar 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[DevTools Bug]: react-hooks/set-state-in-effect misses setState in useEffect for anonymous component callbacks passed to HOFs

1 participant