react-hooks/rules-of-hooks: Improve support for do/while loops (#31720)

This commit is contained in:
Marin Atanasov
2024-12-10 22:46:33 +01:00
committed by GitHub
parent 16367ceb02
commit 7c4a7c9ddf
2 changed files with 29 additions and 3 deletions
@@ -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 <div></div>;
};
`,
},
],
invalid: [
{
@@ -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 ` +