Skip to content

Commit 20c6628

Browse files
committed
fix(no-derived-state): account for non-calls when flagging single state setters
1 parent 68dc35f commit 20c6628

File tree

3 files changed

+20
-41
lines changed

3 files changed

+20
-41
lines changed

src/no-derived-state.js

Lines changed: 13 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
isHOCProp,
1111
getUpstreamReactVariables,
1212
isState,
13-
countStateSetterCalls,
13+
countCalls,
1414
} from "./util/react.js";
1515
import { getCallExpr, getDownstreamRefs } from "./util/ast.js";
1616
import { arraysEqual } from "./util/javascript.js";
@@ -49,35 +49,26 @@ export const rule = {
4949
const stateName = (
5050
useStateNode.id.elements[0] ?? useStateNode.id.elements[1]
5151
)?.name;
52-
const argsRefs = callExpr.arguments.flatMap((arg) =>
53-
getDownstreamRefs(context, arg),
54-
);
55-
const argsUpstreamVariables = argsRefs.flatMap((ref) =>
52+
53+
const argsUpstreamVars = callExpr.arguments
54+
.flatMap((arg) => getDownstreamRefs(context, arg))
55+
.flatMap((ref) => getUpstreamReactVariables(context, ref.resolved));
56+
const depsUpstreamVars = depsRefs.flatMap((ref) =>
5657
getUpstreamReactVariables(context, ref.resolved),
5758
);
58-
const isAllArgsInternal = argsUpstreamVariables.notEmptyEvery(
59+
const isAllArgsInternal = argsUpstreamVars.notEmptyEvery(
5960
(variable) =>
6061
isState(variable) || (isProp(variable) && !isHOCProp(variable)),
6162
);
62-
const isAllArgsInDeps = argsRefs
63-
.filter((ref) =>
64-
ref.resolved.defs.every((def) => def.type !== "Parameter"),
65-
)
66-
.notEmptyEvery((argRef) =>
67-
depsRefs.some((depRef) =>
68-
arraysEqual(
69-
getUpstreamReactVariables(context, argRef.resolved),
70-
getUpstreamReactVariables(context, depRef.resolved),
71-
),
72-
),
73-
);
63+
const isAllArgsInDeps = argsUpstreamVars.notEmptyEvery((argVar) =>
64+
depsUpstreamVars.some((depVar) => argVar.name === depVar.name),
65+
);
7466

7567
if (
7668
isAllArgsInternal ||
77-
(isAllArgsInDeps &&
78-
// In this case the derived state will always be in sync,
79-
// thus it could be computed directly during render
80-
countStateSetterCalls(ref) === 1)
69+
// In this case the derived state will always be in sync,
70+
// thus it could be computed directly during render
71+
(isAllArgsInDeps && countCalls(ref) === 1)
8172
) {
8273
context.report({
8374
node: callExpr,

src/util/react.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -242,8 +242,10 @@ const countUseStates = (context, componentNode) => {
242242
return count;
243243
};
244244

245-
export const countStateSetterCalls = (stateSetterRef) =>
246-
stateSetterRef.resolved.references.length - 1; // -1 for the initial declaration
245+
export const countCalls = (ref) =>
246+
ref.resolved.references.filter(
247+
(ref) => ref.identifier.parent.type === "CallExpression",
248+
).length;
247249

248250
// Returns the component or custom hook that contains the `useEffect` node
249251
const findContainingNode = (node) => {

test/no-derived-state.test.js

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -147,20 +147,6 @@ new MyRuleTester().run(name, rule, {
147147
}
148148
`,
149149
},
150-
{
151-
name: "From external state via member function",
152-
code: js`
153-
function Counter() {
154-
const countGetter = useSomeAPI();
155-
const [count, setCount] = useState(0);
156-
157-
useEffect(() => {
158-
const newCount = countGetter.getCount();
159-
setCount(newCount);
160-
}, [countGetter, setCount]);
161-
}
162-
`,
163-
},
164150
{
165151
name: "Via pure local function",
166152
code: js`
@@ -445,7 +431,7 @@ new MyRuleTester().run(name, rule, {
445431
446432
useEffect(() => {
447433
setSelectedPost(posts[0]);
448-
}, [posts]);
434+
}, [posts, setSelectedPost]);
449435
}
450436
`,
451437
errors: [
@@ -465,7 +451,7 @@ new MyRuleTester().run(name, rule, {
465451
useEffect(() => {
466452
const prefixedName = 'Dr. ' + name;
467453
setFullName(prefixedName)
468-
}, [name]);
454+
}, [name, setFullName]);
469455
}
470456
`,
471457
errors: [
@@ -486,7 +472,7 @@ new MyRuleTester().run(name, rule, {
486472
487473
useEffect(() => {
488474
setLocation(history.location);
489-
}, [history.location]);
475+
}, [history.location, setLocation]);
490476
});
491477
`,
492478
errors: [

0 commit comments

Comments
 (0)