react icon indicating copy to clipboard operation
react copied to clipboard

Bug: [eslint-plugin-react-hooks] incorrectly reports an error when hook is called outside of a loop.

Open akkadaska opened this issue 1 year ago • 1 comments

The following code triggers an ESLint error with the rule eslintreact-hooks/rules-of-hooks stating:

"React Hook 'useState' may be executed more than once. Possibly because it is called in a loop. React Hooks must be called in the exact same order in every component render."

However, the hook useState is not inside the loop, and there is no reason for the error to be thrown.

React version:

React 19.0.0 (latest stable) eslint-plugin-react-hooks 5.1.0 (latest stable)

Steps To Reproduce

  1. Create a functional component with useState hook.
  2. Add a for loop inside the component (but outside the hook).
  3. Run the ESLint with the eslint-react-hooks plugin enabled.
const Component = () => {
  const [state, setState] = useState(0);

  for (let i = 0; i < 10; i++) {
    console.log(i);
  }

  return <div></div>;
};

Link to code example:

https://codesandbox.io/p/devbox/8dlj6f Run npm run lint

The current behavior

ESLint throws an error about useState potentially being called multiple times, even though it is not in a loop.

The expected behavior

No ESLint error should be thrown, as useState is not inside the loop, and should follow the rule of hooks correctly.

akkadaska avatar Dec 06 '24 05:12 akkadaska

We're seeing what appear to be false positives on our CI as well: https://github.com/keycloak/keycloak/actions/runs/12190763028/job/34008503162?pr=35684

jonkoops avatar Dec 06 '24 08:12 jonkoops

Similarly (but having react 18), having a for loop in the body of a functional component, even if it is unrelated to hooks, causes the linter to report that all hooks within that component with may be executed more than once error.

xobotyi avatar Dec 09 '24 10:12 xobotyi

I've added the following code to the valid array in packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js:

    {
      code: normalizeIndent`
        // valid because the loop does not change the number of times the hook is called
        const Component = () => {
          const [state, setState] = useState(0);
          for (let i = 0; i < 10; i++) {
            console.log(i);
          }
          return <div></div>;
        };
      `,
    },

The test fails (but it should work). If I rollback this change: https://github.com/facebook/react/pull/28714/files#diff-5649cc9bc31063c1139ad0f91e98add9c77f641281cba934123db8aeade0b97eL298

The test starts passing (but 8 other use-cases start failing). So we need to make some changes that get all the tests working I think.

I'll try to look in to the code and get this use-case working: https://github.com/skratchdot/react/commit/e4daf2634e752ee63c06422f7427f931a0bb0cdf

skratchdot avatar Dec 09 '24 19:12 skratchdot

Thanks for the reports, folks.

I think we need to handle do/while a bit differently then. I have a few ideas from my past work on #28714, let me push a PR.

tyxla avatar Dec 10 '24 13:12 tyxla

Fix in https://github.com/facebook/react/pull/31720

tyxla avatar Dec 10 '24 13:12 tyxla

Thanks @tyxla ! Since it was a regression, is it planned to release a patch version of eslint-plugin-react-hooks soon ?

xfournet avatar Dec 10 '24 14:12 xfournet

@xfournet I'm not part of the React core team, so I can't tell, but let's summon some folks who were involved with https://github.com/facebook/react/pull/28714.

cc @mofeiZ and @josephsavona for review

tyxla avatar Dec 10 '24 15:12 tyxla

While waiting for the fix to be published, downgrading to [email protected] is a workaround.

s100 avatar Dec 12 '24 11:12 s100

Will there be a patch release soon with this bug fix?

kaiyoma avatar Dec 17 '24 23:12 kaiyoma

npm install eslint-plugin-react-hooks@next works for me

ricsam avatar Jan 16 '25 09:01 ricsam

Considering there has been a fix landed for 2 months now, would it be possible to get a patch release to get this out there?

jonkoops avatar Feb 11 '25 12:02 jonkoops