Skip to content

Commit a65df75

Browse files
committed
fix: fixed 'use-no-direct-set-state-in-use-effect' deferred setState calls detection, closes #1117
1 parent a00a024 commit a65df75

File tree

4 files changed

+74
-20
lines changed

4 files changed

+74
-20
lines changed

packages/plugins/eslint-plugin-react-hooks-extra/src/hooks/use-no-direct-set-state-in-use-effect.ts

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,7 @@ import * as VAR from "@eslint-react/var";
99
import { AST_NODE_TYPES as T } from "@typescript-eslint/types";
1010
import { match } from "ts-pattern";
1111

12-
import {
13-
isFromUseStateCall,
14-
isFunctionOfImmediatelyInvoked,
15-
isSetFunctionCall,
16-
isThenCall,
17-
isVariableDeclaratorFromHookCall,
18-
} from "../utils";
12+
import { isFromUseStateCall, isSetFunctionCall, isThenCall, isVariableDeclaratorFromHookCall } from "../utils";
1913

2014
type CallKind =
2115
| "useEffect"
@@ -98,10 +92,23 @@ export function useNoDirectSetStateInUseEffect<Ctx extends RuleContext>(
9892
}
9993

10094
function getFunctionKind(node: AST.TSESTreeFunction) {
101-
return match<AST.TSESTreeFunction, FunctionKind>(node)
102-
.when(isFunctionOfUseEffectSetup, () => "setup")
103-
.when(isFunctionOfImmediatelyInvoked, () => "immediate")
104-
.otherwise(() => "other");
95+
switch (true) {
96+
case node.async:
97+
case node.parent.type === T.CallExpression && isThenCall(node.parent): {
98+
return "deferred";
99+
}
100+
case node.type !== T.FunctionDeclaration
101+
&& node.parent.type === T.CallExpression
102+
&& node.parent.callee === node: {
103+
return "immediate";
104+
}
105+
case isFunctionOfUseEffectSetup(node): {
106+
return "setup";
107+
}
108+
default: {
109+
return "other";
110+
}
111+
}
105112
}
106113

107114
return {
@@ -128,6 +135,11 @@ export function useNoDirectSetStateInUseEffect<Ctx extends RuleContext>(
128135
match(getCallKind(node))
129136
.with("setState", () => {
130137
switch (true) {
138+
case pEntry.kind === "deferred":
139+
case pEntry.node.async: {
140+
// do nothing, this is a deferred setState call
141+
break;
142+
}
131143
case pEntry.node === setupFunction:
132144
case pEntry.kind === "immediate"
133145
&& AST.findParentNode(pEntry.node, AST.isFunction) === setupFunction: {

packages/plugins/eslint-plugin-react-hooks-extra/src/rules/no-direct-set-state-in-use-effect.spec.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -695,6 +695,57 @@ ruleTester.run(RULE_NAME, rule, {
695695
},
696696
],
697697
},
698+
// https://github.com/Rel1cx/eslint-react/issues/1117
699+
{
700+
code: tsx`
701+
import { useEffect, useState } from "react";
702+
703+
const Component1 = () => {
704+
const [foo, setFoo] = useState(null);
705+
const test = useCallback(() => {
706+
setFoo('') // warning (fine)
707+
fetch().then(() => { setFoo('') }); // warning (problem)
708+
}, [])
709+
useEffect(() => {
710+
test();
711+
fetch().then(() => { setFoo('') }); // no warning (fine)
712+
}, [test]);
713+
}
714+
`,
715+
errors: [
716+
{
717+
messageId: "noDirectSetStateInUseEffect",
718+
data: {
719+
name: "setFoo",
720+
},
721+
},
722+
],
723+
},
724+
{
725+
code: tsx`
726+
import { useEffect, useState } from "react";
727+
728+
const Component1 = () => {
729+
const [foo, setFoo] = useState(null);
730+
const test = () => {
731+
setFoo('') // warning (fine)
732+
fetch().then(() => { setFoo('') }); // no warning (fine)
733+
}
734+
useEffect(() => {
735+
test();
736+
fetch().then(() => { setFoo('') }); // no warning (fine)
737+
}, [test]);
738+
}
739+
`,
740+
errors: [
741+
{
742+
messageId: "noDirectSetStateInUseEffect",
743+
data: {
744+
name: "setFoo",
745+
},
746+
},
747+
],
748+
},
698749
{
699750
code: tsx`
700751
import { useEffect, useState } from "react";

packages/plugins/eslint-plugin-react-hooks-extra/src/utils/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
export * from "./create-rule";
22
export * from "./is-from-hook-call";
33
export * from "./is-from-use-state-call";
4-
export * from "./is-function-of-immediately-invoked";
54
export * from "./is-react-hook-identifier";
65
export * from "./is-set-function-call";
76
export * from "./is-then-call";

packages/plugins/eslint-plugin-react-hooks-extra/src/utils/is-function-of-immediately-invoked.ts

Lines changed: 0 additions & 8 deletions
This file was deleted.

0 commit comments

Comments
 (0)