facebook/react

Bug: `eslint-plugin-react-hooks` false positive with `for` loop in function body

imjordanxd opened this issue ยท 6 comments

React version: N/A
Eslint-plugin-react-hooks version: 5.1.0

Steps To Reproduce

Link to code example:

const Dots = () => {
  const count = 9;
  const [highlightIndex, updateHighlightIndex] = React.useState(0);

  React.useEffect(() => {
    const updateHighlightIndexIntervalID = setInterval(() => {
      updateHighlightIndex((i) => (i + 1) % count);
    }, 200);

    return () => {
      clearInterval(updateHighlightIndexIntervalID);
    };
  }, []);

  const dots: JSX.Element[] = [];
  for (let i = 0; i < count; i++) {
    dots.push(<span key={i} style={{opacity:i === highlightIndex ? 1 : 0.5}}>{i}</span>);
  }
  return <div>{dots}</div>;
};

The current behavior

The linter reports the following error:

ESLint: React Hook "React. 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.(react-hooks/ rules-of-hooks)

This is incorrect. The for loop is correctly reading a reactive variable. No hooks are called conditionally or inside a loop. The code can be rewritten to satisfy the linter but there is nothing wrong with the original code.

The expected behavior

No error is reported. Having a for loop reading a reactive variable should not report an error.

Sometimes, ESLint misinterprets the structure of the code, particularly when using modern JavaScript/TypeScript features.
Try to update the ESLint config or if its till not fixed use a comment to suppress it. There is nothing wrong with the code regarding this

Hi @imjordanxd ,
The problem seems to lie with the eslint-plugin-react-hooks.
I'll take a look if no one is assigned to the outcome yet :)

After some research, it appears that ESLint detects an error when using a classic loop (for, while and do while) in a component that contains hook (useState, useEffect...)

To overcome this problem, you can either :

  • Disable the ESLint rule react-hooks/rules-of-hooks (which I don't recommend)
  • Use Array's .map method instead to get around the problem.

The code would then look like this:

const Dots = () => {
  const count = 9;
  const [highlightIndex, updateHighlightIndex] = React.useState(0);

  React.useEffect(() => {
    const updateHighlightIndexIntervalID = setInterval(() => {
      updateHighlightIndex((i) => (i + 1) % count);
    }, 200);

    return () => {
      clearInterval(updateHighlightIndexIntervalID);
    };
  }, []);

  const dots = [...Array(count)].map((_, i) => (
    <span key={i} style={{ opacity: i === highlightIndex ? 1 : 0.5 }}>
      {i}
    </span>
  ));

  return <div>{dots}</div>;
};

I don't know if this is the behavior expected by eslint-plugin-react-hooks so for the moment I haven't opened a PR to try solving the problem in the codebase.

Looks like a duplicate of #31687

Oh my bad, you're right!

The issue has reportedly been fixed (#31687) but not released yet. Should this be marked as complete now or once the fix is released?