@samitouri / QOS-React / commits / 7c4a7c9ddf

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 ` +