react-hooks/rules-of-hooks: Improve support for `do/while` loops (#31720)
Marin Atanasov committed
Dec 10, 2024 at 23:46 UTC
7c4a7c9ddf2f1c8e223565af1256ea201ec0f303
2 files changed
+29
-3
packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js
+12
@@ -550,6 +550,18 @@ const tests = {
550
// TODO: this should error but doesn't.
551
// errors: [genericError('useState')],
552
},
553
+ {
554
+ code: normalizeIndent`
555
+ // Valid because the hook is outside of the loop
556
+ const Component = () => {
557
+ const [state, setState] = useState(0);
558
+ for (let i = 0; i < 10; i++) {
559
+ console.log(i);
560
+ }
561
+ return <div></div>;
562
+ };
563
+ `,
564
+ },
565
],
566
invalid: [
567
{
packages/eslint-plugin-react-hooks/src/RulesOfHooks.js
+17
-3
@@ -100,6 +100,16 @@ function isInsideComponentOrHook(node) {
100
return false;
101
}
102
103
+function isInsideDoWhileLoop(node) {
104
+ while (node) {
105
+ if (node.type === 'DoWhileStatement') {
106
+ return true;
107
+ }
108
+ node = node.parent;
109
+ }
110
+ return false;
111
+}
112
+
113
function isUseEffectEventIdentifier(node) {
114
if (__EXPERIMENTAL__) {
115
return node.type === 'Identifier' && node.name === 'useEffectEvent';
@@ -295,7 +305,7 @@ export default {
305
if (pathList.has(segment.id)) {
306
const pathArray = Array.from(pathList);
307
const cyclicSegments = pathArray.slice(
298
- pathArray.indexOf(segment.id) - 1,
308
+ pathArray.indexOf(segment.id) + 1,
309
);
310
for (const cyclicSegment of cyclicSegments) {
311
cyclic.add(cyclicSegment);
@@ -485,7 +495,10 @@ export default {
495
for (const hook of reactHooks) {
496
// Report an error if a hook may be called more then once.
497
// `use(...)` can be called in loops.
488
- if (cycled && !isUseIdentifier(hook)) {
498
+ if (
499
+ (cycled || isInsideDoWhileLoop(hook)) &&
500
+ !isUseIdentifier(hook)
501
+ ) {
502
context.report({
503
node: hook,
504
message:
@@ -520,7 +533,8 @@ export default {
533
if (
534
!cycled &&
535
pathsFromStartToEnd !== allPathsFromStartToEnd &&
523
- !isUseIdentifier(hook) // `use(...)` can be called conditionally.
536
+ !isUseIdentifier(hook) && // `use(...)` can be called conditionally.
537
+ !isInsideDoWhileLoop(hook) // wrapping do/while loops are checked separately.
538
) {
539
const message =
540
`React Hook "${getSource(hook)}" is called ` +