diff --git a/packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js b/packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js index 4376d01d824a8..9152999f647c3 100644 --- a/packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js +++ b/packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js @@ -550,6 +550,18 @@ const tests = { // TODO: this should error but doesn't. // errors: [genericError('useState')], }, + { + code: normalizeIndent` + // Valid because the hook is outside of the loop + const Component = () => { + const [state, setState] = useState(0); + for (let i = 0; i < 10; i++) { + console.log(i); + } + return
; + }; + `, + }, ], invalid: [ { diff --git a/packages/eslint-plugin-react-hooks/src/RulesOfHooks.js b/packages/eslint-plugin-react-hooks/src/RulesOfHooks.js index 0b89390898ef4..bb80a24fba690 100644 --- a/packages/eslint-plugin-react-hooks/src/RulesOfHooks.js +++ b/packages/eslint-plugin-react-hooks/src/RulesOfHooks.js @@ -100,6 +100,16 @@ function isInsideComponentOrHook(node) { return false; } +function isInsideDoWhileLoop(node) { + while (node) { + if (node.type === 'DoWhileStatement') { + return true; + } + node = node.parent; + } + return false; +} + function isUseEffectEventIdentifier(node) { if (__EXPERIMENTAL__) { return node.type === 'Identifier' && node.name === 'useEffectEvent'; @@ -295,7 +305,7 @@ export default { if (pathList.has(segment.id)) { const pathArray = Array.from(pathList); const cyclicSegments = pathArray.slice( - pathArray.indexOf(segment.id) - 1, + pathArray.indexOf(segment.id) + 1, ); for (const cyclicSegment of cyclicSegments) { cyclic.add(cyclicSegment); @@ -485,7 +495,10 @@ export default { for (const hook of reactHooks) { // Report an error if a hook may be called more then once. // `use(...)` can be called in loops. - if (cycled && !isUseIdentifier(hook)) { + if ( + (cycled || isInsideDoWhileLoop(hook)) && + !isUseIdentifier(hook) + ) { context.report({ node: hook, message: @@ -520,7 +533,8 @@ export default { if ( !cycled && pathsFromStartToEnd !== allPathsFromStartToEnd && - !isUseIdentifier(hook) // `use(...)` can be called conditionally. + !isUseIdentifier(hook) && // `use(...)` can be called conditionally. + !isInsideDoWhileLoop(hook) // wrapping do/while loops are checked separately. ) { const message = `React Hook "${getSource(hook)}" is called ` +