From d72b4f7ecf3405b9b6d61d39a30eeb58d8f1c2c8 Mon Sep 17 00:00:00 2001
From: possum-enjoyer <15869016+possum-enjoyer@users.noreply.github.com>
Date: Sat, 15 Nov 2025 01:10:10 +0100
Subject: [PATCH 1/9] wip: extend no-unneccesary-use-callback
---
.../rules/no-unnecessary-use-callback.spec.ts | 161 ++++++++++++++++++
.../src/rules/no-unnecessary-use-callback.ts | 40 ++++-
2 files changed, 197 insertions(+), 4 deletions(-)
diff --git a/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-callback.spec.ts b/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-callback.spec.ts
index 959a4e2db..f9e7bb2e0 100644
--- a/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-callback.spec.ts
+++ b/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-callback.spec.ts
@@ -405,6 +405,103 @@ ruleTester.run(RULE_NAME, rule, {
},
},
},
+
+ {
+ code: tsx`
+ import {useCallback, useState, useEffect} from 'react';
+
+ function App({ items }) {
+ const [test, setTest] = useState(0);
+
+ const updateTest = useCallback(() => {setTest(items.length)}, []);
+
+ useEffect(() => {
+ updateTest();
+ }, [updateTest]);
+
+ return
items
;
+ }
+ `,
+ errors: [
+ {
+ messageId: "noUnnecessaryUseCallbackInsideUseEffect",
+ },
+ ],
+ settings: {
+ "react-x": {
+ importSource: "react",
+ },
+ },
+ },
+ {
+ code: tsx`
+ import {useCallback, useState, useEffect} from 'react';
+
+ function App({ items }) {
+ const [test, setTest] = useState(0);
+
+ const updateTest = useCallback(() => {setTest(items.length)}, []);
+
+ useEffect(() => {
+ updateTest();
+ }, [updateTest]);
+
+ return items
;
+ }
+
+ function App({ items }) {
+ const [test, setTest] = useState(0);
+
+ const updateTest = useCallback(() => {setTest(items.length)}, []);
+
+ useEffect(() => {
+ updateTest();
+ }, [updateTest]);
+
+ return items
;
+ }
+ `,
+ errors: [
+ {
+ messageId: "noUnnecessaryUseCallbackInsideUseEffect",
+ },
+ {
+ messageId: "noUnnecessaryUseCallbackInsideUseEffect",
+ },
+ ],
+ settings: {
+ "react-x": {
+ importSource: "react",
+ },
+ },
+ },
+ {
+ code: tsx`
+ const { useCallback, useEffect } = require("@pika/react");
+
+ function App({ items }) {
+ const [test, setTest] = useState(0);
+
+ const updateTest = useCallback(() => {setTest(items.length)}, []);
+
+ useEffect(() => {
+ updateTest();
+ }, [updateTest]);
+
+ return items
;
+ }
+ `,
+ errors: [
+ {
+ messageId: "noUnnecessaryUseCallbackInsideUseEffect",
+ },
+ ],
+ settings: {
+ "react-x": {
+ importSource: "@pika/react",
+ },
+ },
+ },
],
valid: [
...allValid,
@@ -501,5 +598,69 @@ ruleTester.run(RULE_NAME, rule, {
const refItem = useCallback(cb, deps)
};
`,
+ tsx`
+ import { useCallback, useState, useEffect } from 'react';
+
+ function App({ items }) {
+ const [test, setTest] = useState(items.length);
+
+ const updateTest = useCallback(() => { setTest(items.length + 1) }, [setTest, items]);
+
+ useEffect(function () {
+ function foo() {
+ updateTest();
+ }
+
+ foo();
+
+ updateTest();
+ }, [updateTest])
+
+ return updateTest()}>{test}
;
+ }
+ `,
+ tsx`
+ import { useCallback, useState, useEffect } from 'react';
+
+ const Component = () => {
+ const [test, setTest] = useState(items.length);
+
+ const updateTest = useCallback(() => { setTest(items.length + 1) }, [setTest, items]);
+
+ useEffect(() => {
+ // some condition
+ doSomeTask();
+ }, [doSomeTask]);
+
+ useEffect(() => {
+ // some condition
+ doSomeTask();
+ }, [doSomeTask]);
+
+ return ;
+ };
+ `,
+ tsx`
+ import { useCallback, useState, useEffect } from 'react';
+
+ const Component = () => {
+ const [test, setTest] = useState(items.length);
+
+ const updateTest = useCallback(() => { setTest(items.length + 1) }, [setTest, items]);
+
+ return doSomeTask()} />;
+ };
+ `,
+ tsx`
+ import { useCallback, useState, useEffect } from 'react';
+
+ const Component = () => {
+ const [test, setTest] = useState(items.length);
+
+ const updateTest = useCallback(() => { setTest(items.length + 1) }, [setTest, items]);
+
+ return
;
+ };
+ `,
],
});
diff --git a/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-callback.ts b/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-callback.ts
index a080f1e95..324f7ae7d 100644
--- a/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-callback.ts
+++ b/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-callback.ts
@@ -1,13 +1,13 @@
import * as AST from "@eslint-react/ast";
-import { isUseCallbackCall } from "@eslint-react/core";
+import { isUseCallbackCall, isUseEffectLikeCall } from "@eslint-react/core";
import { identity } from "@eslint-react/eff";
import type { RuleContext, RuleFeature } from "@eslint-react/shared";
import { findVariable, getChildScopes, getVariableDefinitionNode } from "@eslint-react/var";
import { AST_NODE_TYPES as T } from "@typescript-eslint/types";
-import type { RuleListener } from "@typescript-eslint/utils/ts-eslint";
+import { isIdentifier } from "@typescript-eslint/utils/ast-utils";
+import { type RuleListener } from "@typescript-eslint/utils/ts-eslint";
import type { CamelCase } from "string-ts";
import { match } from "ts-pattern";
-
import { createRule } from "../utils";
export const RULE_NAME = "no-unnecessary-use-callback";
@@ -16,7 +16,7 @@ export const RULE_FEATURES = [
"EXP",
] as const satisfies RuleFeature[];
-export type MessageID = CamelCase
;
+export type MessageID = CamelCase | "noUnnecessaryUseCallbackInsideUseEffect";
export default createRule<[], MessageID>({
meta: {
@@ -28,6 +28,7 @@ export default createRule<[], MessageID>({
messages: {
noUnnecessaryUseCallback:
"An 'useCallback' with empty deps and no references to the component scope may be unnecessary.",
+ noUnnecessaryUseCallbackInsideUseEffect: "{{name}} is only used inside 1 useEffect may be unnecessary.",
},
schema: [],
},
@@ -39,13 +40,16 @@ export default createRule<[], MessageID>({
export function create(context: RuleContext): RuleListener {
// Fast path: skip if `useCallback` is not present in the file
if (!context.sourceCode.text.includes("useCallback")) return {};
+
return {
CallExpression(node) {
if (!isUseCallbackCall(node)) {
return;
}
+
const initialScope = context.sourceCode.getScope(node);
const component = context.sourceCode.getScope(node).block;
+
if (!AST.isFunction(component)) {
return;
}
@@ -99,5 +103,33 @@ export function create(context: RuleContext): RuleListener {
});
}
},
+ VariableDeclarator(node) {
+ if (!context.sourceCode.text.includes("useEffect")) {
+ return;
+ }
+
+ if (isIdentifier(node.id)) {
+ const references = context.sourceCode.getDeclaredVariables(node)[0]?.references ?? [];
+ const usages = references.filter((ref) => !(ref.init ?? false));
+
+ const size = usages.reduce((set, usage) => {
+ const effect = AST.findParentNode(usage.identifier, (node) => {
+ return isUseEffectLikeCall(node);
+ });
+ set.add(effect ?? node.parent);
+ return set;
+ }, new Set()).size;
+
+ if (size !== 1) {
+ return;
+ }
+
+ context.report({
+ messageId: "noUnnecessaryUseCallbackInsideUseEffect",
+ node,
+ data: { name: node.id.name },
+ });
+ }
+ },
};
}
From 25eeb05523aac952f4212256e5060ec87c5b86c3 Mon Sep 17 00:00:00 2001
From: possum-enjoyer <15869016+possum-enjoyer@users.noreply.github.com>
Date: Sat, 15 Nov 2025 02:24:29 +0100
Subject: [PATCH 2/9] missing a word inside the message :(
---
.../src/rules/no-unnecessary-use-callback.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-callback.ts b/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-callback.ts
index 324f7ae7d..c1a906e45 100644
--- a/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-callback.ts
+++ b/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-callback.ts
@@ -28,7 +28,7 @@ export default createRule<[], MessageID>({
messages: {
noUnnecessaryUseCallback:
"An 'useCallback' with empty deps and no references to the component scope may be unnecessary.",
- noUnnecessaryUseCallbackInsideUseEffect: "{{name}} is only used inside 1 useEffect may be unnecessary.",
+ noUnnecessaryUseCallbackInsideUseEffect: "{{name}} is only used inside 1 useEffect which may be unnecessary.",
},
schema: [],
},
From dd2e632540983f60c72693cb18668531929ba71f Mon Sep 17 00:00:00 2001
From: possum-enjoyer <15869016+possum-enjoyer@users.noreply.github.com>
Date: Sat, 15 Nov 2025 03:03:29 +0100
Subject: [PATCH 3/9] minor refactoring for better readability
---
.../rules/no-unnecessary-use-callback.spec.ts | 15 ++++---
.../src/rules/no-unnecessary-use-callback.ts | 44 ++++++++++---------
2 files changed, 32 insertions(+), 27 deletions(-)
diff --git a/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-callback.spec.ts b/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-callback.spec.ts
index f9e7bb2e0..9f51a8369 100644
--- a/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-callback.spec.ts
+++ b/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-callback.spec.ts
@@ -475,7 +475,7 @@ ruleTester.run(RULE_NAME, rule, {
},
},
},
- {
+ {
code: tsx`
const { useCallback, useEffect } = require("@pika/react");
@@ -598,6 +598,7 @@ ruleTester.run(RULE_NAME, rule, {
const refItem = useCallback(cb, deps)
};
`,
+
tsx`
import { useCallback, useState, useEffect } from 'react';
@@ -629,13 +630,13 @@ ruleTester.run(RULE_NAME, rule, {
useEffect(() => {
// some condition
- doSomeTask();
- }, [doSomeTask]);
+ updateTest();
+ }, [updateTest]);
useEffect(() => {
// some condition
- doSomeTask();
- }, [doSomeTask]);
+ updateTest();
+ }, [updateTest]);
return ;
};
@@ -648,7 +649,7 @@ ruleTester.run(RULE_NAME, rule, {
const updateTest = useCallback(() => { setTest(items.length + 1) }, [setTest, items]);
- return doSomeTask()} />;
+ return
updateTest()} />;
};
`,
tsx`
@@ -659,7 +660,7 @@ ruleTester.run(RULE_NAME, rule, {
const updateTest = useCallback(() => { setTest(items.length + 1) }, [setTest, items]);
- return
;
+ return
;
};
`,
],
diff --git a/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-callback.ts b/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-callback.ts
index c1a906e45..62bcef985 100644
--- a/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-callback.ts
+++ b/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-callback.ts
@@ -108,28 +108,32 @@ export function create(context: RuleContext
): RuleListener {
return;
}
- if (isIdentifier(node.id)) {
- const references = context.sourceCode.getDeclaredVariables(node)[0]?.references ?? [];
- const usages = references.filter((ref) => !(ref.init ?? false));
-
- const size = usages.reduce((set, usage) => {
- const effect = AST.findParentNode(usage.identifier, (node) => {
- return isUseEffectLikeCall(node);
- });
- set.add(effect ?? node.parent);
- return set;
- }, new Set()).size;
-
- if (size !== 1) {
- return;
- }
+ if (!isUseCallbackCall(node.init ?? undefined)) {
+ return;
+ }
- context.report({
- messageId: "noUnnecessaryUseCallbackInsideUseEffect",
- node,
- data: { name: node.id.name },
- });
+ if (!isIdentifier(node.id)) {
+ return;
}
+
+ const references = context.sourceCode.getDeclaredVariables(node)[0]?.references ?? [];
+ const usages = references.filter((ref) => !(ref.init ?? false));
+
+ const filteredUsageAmount = usages.reduce((set, usage) => {
+ const effect = AST.findParentNode(usage.identifier, (node) => isUseEffectLikeCall(node));
+ // TODO Better check for outside of useEffect usage
+ return set.add(effect ?? 'outside');
+ }, new Set());
+
+ if (filteredUsageAmount.size !== 1 || filteredUsageAmount.has('outside')) {
+ return;
+ }
+
+ context.report({
+ messageId: "noUnnecessaryUseCallbackInsideUseEffect",
+ node,
+ data: { name: node.id.name },
+ });
},
};
}
From 0d0b32f5a399d8e65b4e30b90dead251a2700b56 Mon Sep 17 00:00:00 2001
From: possum-enjoyer <15869016+possum-enjoyer@users.noreply.github.com>
Date: Sat, 15 Nov 2025 13:17:19 +0100
Subject: [PATCH 4/9] refactor: simplify logic
---
.../src/rules/no-unnecessary-use-callback.ts | 15 ++++++++++-----
1 file changed, 10 insertions(+), 5 deletions(-)
diff --git a/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-callback.ts b/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-callback.ts
index 62bcef985..156f33ac5 100644
--- a/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-callback.ts
+++ b/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-callback.ts
@@ -3,6 +3,7 @@ import { isUseCallbackCall, isUseEffectLikeCall } from "@eslint-react/core";
import { identity } from "@eslint-react/eff";
import type { RuleContext, RuleFeature } from "@eslint-react/shared";
import { findVariable, getChildScopes, getVariableDefinitionNode } from "@eslint-react/var";
+import type { TSESTree } from "@typescript-eslint/types";
import { AST_NODE_TYPES as T } from "@typescript-eslint/types";
import { isIdentifier } from "@typescript-eslint/utils/ast-utils";
import { type RuleListener } from "@typescript-eslint/utils/ts-eslint";
@@ -118,14 +119,18 @@ export function create(context: RuleContext): RuleListener {
const references = context.sourceCode.getDeclaredVariables(node)[0]?.references ?? [];
const usages = references.filter((ref) => !(ref.init ?? false));
+ const effectSet = new Set();
- const filteredUsageAmount = usages.reduce((set, usage) => {
+ const isUsedOutSideOfEffect = usages.some((usage) => {
const effect = AST.findParentNode(usage.identifier, (node) => isUseEffectLikeCall(node));
- // TODO Better check for outside of useEffect usage
- return set.add(effect ?? 'outside');
- }, new Set());
+ if (effect == null) {
+ return true;
+ }
+ effectSet.add(effect);
+ return false;
+ });
- if (filteredUsageAmount.size !== 1 || filteredUsageAmount.has('outside')) {
+ if (isUsedOutSideOfEffect || effectSet.size !== 1) {
return;
}
From b8437bcfe192fe86c948e7c76b731ea1af3d1f46 Mon Sep 17 00:00:00 2001
From: possum-enjoyer <15869016+possum-enjoyer@users.noreply.github.com>
Date: Sat, 15 Nov 2025 23:10:58 +0100
Subject: [PATCH 5/9] code cleanup
---
.../rules/no-unnecessary-use-callback.spec.ts | 36 +++++++++----------
.../src/rules/no-unnecessary-use-callback.ts | 15 ++++----
2 files changed, 25 insertions(+), 26 deletions(-)
diff --git a/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-callback.spec.ts b/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-callback.spec.ts
index 9f51a8369..580d0a510 100644
--- a/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-callback.spec.ts
+++ b/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-callback.spec.ts
@@ -600,7 +600,7 @@ ruleTester.run(RULE_NAME, rule, {
`,
tsx`
- import { useCallback, useState, useEffect } from 'react';
+ import { useCallback, useState, useEffect } from 'react';
function App({ items }) {
const [test, setTest] = useState(items.length);
@@ -621,30 +621,30 @@ ruleTester.run(RULE_NAME, rule, {
}
`,
tsx`
- import { useCallback, useState, useEffect } from 'react';
+ import { useCallback, useState, useEffect } from 'react';
- const Component = () => {
- const [test, setTest] = useState(items.length);
+ const Component = () => {
+ const [test, setTest] = useState(items.length);
- const updateTest = useCallback(() => { setTest(items.length + 1) }, [setTest, items]);
+ const updateTest = useCallback(() => { setTest(items.length + 1) }, [setTest, items]);
- useEffect(() => {
- // some condition
- updateTest();
- }, [updateTest]);
+ useEffect(() => {
+ // some condition
+ updateTest();
+ }, [updateTest]);
- useEffect(() => {
- // some condition
- updateTest();
- }, [updateTest]);
+ useEffect(() => {
+ // some condition
+ updateTest();
+ }, [updateTest]);
- return ;
+ return ;
};
`,
tsx`
- import { useCallback, useState, useEffect } from 'react';
+ import { useCallback, useState, useEffect } from 'react';
- const Component = () => {
+ const Component = () => {
const [test, setTest] = useState(items.length);
const updateTest = useCallback(() => { setTest(items.length + 1) }, [setTest, items]);
@@ -653,9 +653,9 @@ ruleTester.run(RULE_NAME, rule, {
};
`,
tsx`
- import { useCallback, useState, useEffect } from 'react';
+ import { useCallback, useState, useEffect } from 'react';
- const Component = () => {
+ const Component = () => {
const [test, setTest] = useState(items.length);
const updateTest = useCallback(() => { setTest(items.length + 1) }, [setTest, items]);
diff --git a/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-callback.ts b/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-callback.ts
index 156f33ac5..eaf481c93 100644
--- a/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-callback.ts
+++ b/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-callback.ts
@@ -121,19 +121,18 @@ export function create(context: RuleContext): RuleListener {
const usages = references.filter((ref) => !(ref.init ?? false));
const effectSet = new Set();
- const isUsedOutSideOfEffect = usages.some((usage) => {
+ for (const usage of usages) {
const effect = AST.findParentNode(usage.identifier, (node) => isUseEffectLikeCall(node));
+
if (effect == null) {
- return true;
+ return;
}
- effectSet.add(effect);
- return false;
- });
- if (isUsedOutSideOfEffect || effectSet.size !== 1) {
- return;
+ effectSet.add(effect);
+ if (effectSet.size > 1) {
+ return;
+ }
}
-
context.report({
messageId: "noUnnecessaryUseCallbackInsideUseEffect",
node,
From 7d7719fcb640f58a367ffb1b844ba76ca3e92619 Mon Sep 17 00:00:00 2001
From: possum-enjoyer <15869016+possum-enjoyer@users.noreply.github.com>
Date: Sat, 15 Nov 2025 23:11:33 +0100
Subject: [PATCH 6/9] add useCallback rule features to useMemo
---
.../src/rules/no-unnecessary-use-memo.spec.ts | 100 ++++++++++++++++++
.../src/rules/no-unnecessary-use-memo.ts | 43 +++++++-
2 files changed, 140 insertions(+), 3 deletions(-)
diff --git a/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-memo.spec.ts b/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-memo.spec.ts
index 723bc1067..371e1df6b 100644
--- a/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-memo.spec.ts
+++ b/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-memo.spec.ts
@@ -96,6 +96,64 @@ ruleTester.run(RULE_NAME, rule, {
},
],
},
+
+ {
+ code: tsx`
+ import {useMemo, useState, useEffect} from 'react';
+
+ function veryHeavyCalculation(items) {
+ console.log(items)
+ return items
+ }
+
+ function App({ items }) {
+ const [test, setTest] = useState(0);
+ const heavyStuff = useMemo(() => veryHeavyCalculation(items), [items]);
+
+ useEffect(() => {
+ setTest(heavyStuff.length)
+ }, [heavyStuff]);
+
+ return items
;
+ }
+ `,
+ errors: [
+ {
+ messageId: "noUnnecessaryUseMemoInsideUseEffect",
+ },
+ ],
+ settings: {
+ "react-x": {
+ importSource: "react",
+ },
+ },
+ },
+ {
+ code: tsx`
+ const { useMemo, useState, useEffect } = require("@pika/react");
+
+ function App({ items }) {
+ const [test, setTest] = useState(0);
+ const heavyStuff = useMemo(() => veryHeavyCalculation(items), [items]);
+
+ useEffect(() => {
+ setTest(heavyStuff.length)
+ }, [heavyStuff]);
+
+ return items
;
+ }
+ `,
+ errors: [
+ {
+ messageId: "noUnnecessaryUseMemoInsideUseEffect",
+ },
+ ],
+ settings: {
+ "react-x": {
+ importSource: "@pika/react",
+ },
+ },
+ },
],
valid: [
...allValid,
@@ -240,5 +298,47 @@ ruleTester.run(RULE_NAME, rule, {
return null;
}
`,
+
+ tsx`
+ import { useMemo, useState, useEffect } from 'react';
+
+ function App({ items }) {
+ const [test, setTest] = useState(0);
+ const heavyStuff = useMemo(() => veryHeavyCalculation(items), [items]);
+
+ useEffect(() => {
+ setTest(heavyStuff.length)
+ }, [heavyStuff]);
+
+ return {heavyStuff.length}
;
+ }
+ `,
+ tsx`
+ import { useMemo, useState, useEffect } from 'react';
+
+ function App({ items }) {
+ const [test, setTest] = useState(0);
+ const heavyStuff = useMemo(() => veryHeavyCalculation(items), [items]);
+
+ useEffect(() => {
+ setTest(heavyStuff.length)
+ }, [heavyStuff]);
+
+ useEffect(() => {
+ console.log(heavyStuff)
+ }, [heavyStuff]);
+
+ return {heavyStuff.length}
;
+ }
+ `,
+ tsx`
+ import { useMemo } from 'react';
+
+ function App({ items }) {
+ const heavyStuff = useMemo(() => veryHeavyCalculation(items), [items]);
+
+ return {heavyStuff.length}
;
+ }
+ `,
],
});
diff --git a/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-memo.ts b/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-memo.ts
index 6c66f6b4f..bcced6f49 100644
--- a/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-memo.ts
+++ b/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-memo.ts
@@ -1,13 +1,14 @@
import * as AST from "@eslint-react/ast";
-import { isUseMemoCall } from "@eslint-react/core";
+import { isUseEffectLikeCall, isUseMemoCall } from "@eslint-react/core";
import { identity } from "@eslint-react/eff";
import type { RuleContext, RuleFeature } from "@eslint-react/shared";
import { findVariable, getChildScopes, getVariableDefinitionNode } from "@eslint-react/var";
-import { AST_NODE_TYPES as T } from "@typescript-eslint/types";
+import { AST_NODE_TYPES as T, type TSESTree } from "@typescript-eslint/types";
import type { RuleListener } from "@typescript-eslint/utils/ts-eslint";
import type { CamelCase } from "string-ts";
import { match } from "ts-pattern";
+import { isIdentifier } from "@typescript-eslint/utils/ast-utils";
import { createRule } from "../utils";
export const RULE_NAME = "no-unnecessary-use-memo";
@@ -16,7 +17,7 @@ export const RULE_FEATURES = [
"EXP",
] as const satisfies RuleFeature[];
-export type MessageID = CamelCase;
+export type MessageID = CamelCase | "noUnnecessaryUseMemoInsideUseEffect";
export default createRule<[], MessageID>({
meta: {
@@ -27,6 +28,7 @@ export default createRule<[], MessageID>({
},
messages: {
noUnnecessaryUseMemo: "An 'useMemo' with empty deps and no references to the component scope may be unnecessary.",
+ noUnnecessaryUseMemoInsideUseEffect: "{{name}} is only used inside 1 useEffect which may be unnecessary.",
},
schema: [],
},
@@ -105,5 +107,40 @@ export function create(context: RuleContext): RuleListener {
});
}
},
+ VariableDeclarator(node) {
+ if (!context.sourceCode.text.includes("useEffect")) {
+ return;
+ }
+
+ if (!isUseMemoCall(node.init ?? undefined)) {
+ return;
+ }
+
+ if (!isIdentifier(node.id)) {
+ return;
+ }
+
+ const references = context.sourceCode.getDeclaredVariables(node)[0]?.references ?? [];
+ const usages = references.filter((ref) => !(ref.init ?? false));
+ const effectSet = new Set();
+
+ for (const usage of usages) {
+ const effect = AST.findParentNode(usage.identifier, (node) => isUseEffectLikeCall(node));
+
+ if (effect == null) {
+ return;
+ }
+
+ effectSet.add(effect);
+ if (effectSet.size > 1) {
+ return;
+ }
+ }
+ context.report({
+ messageId: "noUnnecessaryUseMemoInsideUseEffect",
+ node,
+ data: { name: node.id.name },
+ });
+ },
};
}
From 186f44a0135422470070c12d4e5c063f8e8f8e13 Mon Sep 17 00:00:00 2001
From: possum-enjoyer <15869016+possum-enjoyer@users.noreply.github.com>
Date: Thu, 20 Nov 2025 18:49:47 +0100
Subject: [PATCH 7/9] chore: indentation for no-unnecessary-use-callback tests
---
.../rules/no-unnecessary-use-callback.spec.ts | 43 +++++++++++++++----
1 file changed, 35 insertions(+), 8 deletions(-)
diff --git a/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-callback.spec.ts b/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-callback.spec.ts
index 580d0a510..918e39414 100644
--- a/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-callback.spec.ts
+++ b/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-callback.spec.ts
@@ -413,7 +413,7 @@ ruleTester.run(RULE_NAME, rule, {
function App({ items }) {
const [test, setTest] = useState(0);
- const updateTest = useCallback(() => {setTest(items.length)}, []);
+ const updateTest = useCallback(() => {setTest(items.length)}, [items]);
useEffect(() => {
updateTest();
@@ -440,7 +440,34 @@ ruleTester.run(RULE_NAME, rule, {
function App({ items }) {
const [test, setTest] = useState(0);
- const updateTest = useCallback(() => {setTest(items.length)}, []);
+ const updateTest = useCallback(() => {console.log('test')}, []);
+
+ useEffect(() => {
+ updateTest();
+ }, [updateTest]);
+
+ return items
;
+ }
+ `,
+ errors: [
+ {
+ messageId: "noUnnecessaryUseCallback",
+ },
+ ],
+ settings: {
+ "react-x": {
+ importSource: "react",
+ },
+ },
+ },
+ {
+ code: tsx`
+ import {useCallback, useState, useEffect} from 'react';
+
+ function App({ items }) {
+ const [test, setTest] = useState(0);
+
+ const updateTest = useCallback(() => {setTest(items.length)}, [items]);
useEffect(() => {
updateTest();
@@ -449,10 +476,10 @@ ruleTester.run(RULE_NAME, rule, {
return items
;
}
- function App({ items }) {
+ function App({ items }) {
const [test, setTest] = useState(0);
- const updateTest = useCallback(() => {setTest(items.length)}, []);
+ const updateTest = useCallback(() => {setTest(items.length)}, [items]);
useEffect(() => {
updateTest();
@@ -482,7 +509,7 @@ ruleTester.run(RULE_NAME, rule, {
function App({ items }) {
const [test, setTest] = useState(0);
- const updateTest = useCallback(() => {setTest(items.length)}, []);
+ const updateTest = useCallback(() => {setTest(items.length)}, [items]);
useEffect(() => {
updateTest();
@@ -623,7 +650,7 @@ ruleTester.run(RULE_NAME, rule, {
tsx`
import { useCallback, useState, useEffect } from 'react';
- const Component = () => {
+ const Component = () => {
const [test, setTest] = useState(items.length);
const updateTest = useCallback(() => { setTest(items.length + 1) }, [setTest, items]);
@@ -644,7 +671,7 @@ ruleTester.run(RULE_NAME, rule, {
tsx`
import { useCallback, useState, useEffect } from 'react';
- const Component = () => {
+ const Component = () => {
const [test, setTest] = useState(items.length);
const updateTest = useCallback(() => { setTest(items.length + 1) }, [setTest, items]);
@@ -655,7 +682,7 @@ ruleTester.run(RULE_NAME, rule, {
tsx`
import { useCallback, useState, useEffect } from 'react';
- const Component = () => {
+ const Component = () => {
const [test, setTest] = useState(items.length);
const updateTest = useCallback(() => { setTest(items.length + 1) }, [setTest, items]);
From db41cbed93ff18215b7548821be4f56144c98838 Mon Sep 17 00:00:00 2001
From: possum-enjoyer <15869016+possum-enjoyer@users.noreply.github.com>
Date: Sat, 22 Nov 2025 14:03:49 +0100
Subject: [PATCH 8/9] update lint messages
---
.../src/rules/no-unnecessary-use-callback.ts | 3 ++-
.../eslint-plugin-react-x/src/rules/no-unnecessary-use-memo.ts | 3 ++-
2 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-callback.ts b/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-callback.ts
index eaf481c93..963be7a66 100644
--- a/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-callback.ts
+++ b/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-callback.ts
@@ -29,7 +29,8 @@ export default createRule<[], MessageID>({
messages: {
noUnnecessaryUseCallback:
"An 'useCallback' with empty deps and no references to the component scope may be unnecessary.",
- noUnnecessaryUseCallbackInsideUseEffect: "{{name}} is only used inside 1 useEffect which may be unnecessary.",
+ noUnnecessaryUseCallbackInsideUseEffect:
+ "{{name}} is only used inside 1 useEffect, which may be unnecessary. You can move the computation into useEffect directly and merge the dependency arrays.",
},
schema: [],
},
diff --git a/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-memo.ts b/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-memo.ts
index bcced6f49..eca742b54 100644
--- a/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-memo.ts
+++ b/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-memo.ts
@@ -28,7 +28,8 @@ export default createRule<[], MessageID>({
},
messages: {
noUnnecessaryUseMemo: "An 'useMemo' with empty deps and no references to the component scope may be unnecessary.",
- noUnnecessaryUseMemoInsideUseEffect: "{{name}} is only used inside 1 useEffect which may be unnecessary.",
+ noUnnecessaryUseMemoInsideUseEffect:
+ "{{name}} is only used inside 1 useEffect, which may be unnecessary. You can move the computation into useEffect directly and merge the dependency arrays.",
},
schema: [],
},
From f788883d7ca17710635420e6373bf2a34774ffe4 Mon Sep 17 00:00:00 2001
From: possum-enjoyer <15869016+possum-enjoyer@users.noreply.github.com>
Date: Tue, 25 Nov 2025 22:32:01 +0100
Subject: [PATCH 9/9] update documentation for rules fix both rules appearing
at the same time. Now the original error message takes precedence
---
.../src/rules/no-unnecessary-use-callback.mdx | 30 +++
.../src/rules/no-unnecessary-use-callback.ts | 72 ++++---
.../src/rules/no-unnecessary-use-memo.mdx | 193 ++++++++++--------
.../src/rules/no-unnecessary-use-memo.ts | 73 ++++---
4 files changed, 223 insertions(+), 145 deletions(-)
diff --git a/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-callback.mdx b/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-callback.mdx
index 94667289b..457055db0 100644
--- a/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-callback.mdx
+++ b/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-callback.mdx
@@ -29,6 +29,7 @@ react-x/no-unnecessary-use-callback
Disallow unnecessary usage of `useCallback`.
React Hooks `useCallback` has empty dependencies array like what's in the examples, are unnecessary. The hook can be removed and it's value can be created in the component body or hoisted to the outer scope of the component.
+If the calculated function is only used inside one useEffect the calculation can be moved inside the useEffect Function.
## Examples
@@ -46,6 +47,22 @@ function MyComponent() {
}
```
+
+```tsx
+import { Button, MantineTheme } from "@mantine/core";
+import React, { useCallback, useEffect } from "react";
+
+function MyComponent({items}: {items: string[]}) {
+ const updateTest = useCallback(() => {console.log(items.length)}, [items]);
+
+ useEffect(() => {
+ updateTest();
+ }, [updateTest]);
+
+ return Hello World
;
+}
+```
+
### Passing
```tsx
@@ -60,6 +77,19 @@ function MyComponent() {
}
```
+```tsx
+import { Button, MantineTheme } from "@mantine/core";
+import React, { useEffect } from "react";
+
+function MyComponent({items}: {items: string[]}) {
+ useEffect(() => {
+ console.log(items.length);
+ }, [items]);
+
+ return Hello World
;
+}
+```
+
## Implementation
- [Rule Source](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-callback.ts)
diff --git a/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-callback.ts b/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-callback.ts
index 963be7a66..7ec3cf9c4 100644
--- a/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-callback.ts
+++ b/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-callback.ts
@@ -1,12 +1,12 @@
import * as AST from "@eslint-react/ast";
import { isUseCallbackCall, isUseEffectLikeCall } from "@eslint-react/core";
import { identity } from "@eslint-react/eff";
-import type { RuleContext, RuleFeature } from "@eslint-react/shared";
+import { type RuleContext, type RuleFeature, report } from "@eslint-react/shared";
import { findVariable, getChildScopes, getVariableDefinitionNode } from "@eslint-react/var";
import type { TSESTree } from "@typescript-eslint/types";
import { AST_NODE_TYPES as T } from "@typescript-eslint/types";
-import { isIdentifier } from "@typescript-eslint/utils/ast-utils";
-import { type RuleListener } from "@typescript-eslint/utils/ts-eslint";
+import { isIdentifier, isVariableDeclarator } from "@typescript-eslint/utils/ast-utils";
+import { type ReportDescriptor, type RuleListener, type SourceCode } from "@typescript-eslint/utils/ts-eslint";
import type { CamelCase } from "string-ts";
import { match } from "ts-pattern";
import { createRule } from "../utils";
@@ -49,6 +49,8 @@ export function create(context: RuleContext): RuleListener {
return;
}
+ const checkForUsageInsideUseEffectReport = checkForUsageInsideUseEffect(context.sourceCode, node);
+
const initialScope = context.sourceCode.getScope(node);
const component = context.sourceCode.getScope(node).block;
@@ -73,8 +75,10 @@ export function create(context: RuleContext): RuleListener {
.otherwise(() => false);
if (!hasEmptyDeps) {
+ report(context)(checkForUsageInsideUseEffectReport);
return;
}
+
const arg0Node = match(arg0)
.with({ type: T.ArrowFunctionExpression }, (n) => {
if (n.body.type === T.ArrowFunctionExpression) {
@@ -103,42 +107,46 @@ export function create(context: RuleContext): RuleListener {
messageId: "noUnnecessaryUseCallback",
node,
});
- }
- },
- VariableDeclarator(node) {
- if (!context.sourceCode.text.includes("useEffect")) {
return;
}
+ report(context)(checkForUsageInsideUseEffectReport);
+ },
+ };
+}
- if (!isUseCallbackCall(node.init ?? undefined)) {
- return;
- }
+function checkForUsageInsideUseEffect(
+ sourceCode: Readonly,
+ node: TSESTree.CallExpression,
+): ReportDescriptor | undefined {
+ if (!/use\w*Effect/u.test(sourceCode.text)) return;
- if (!isIdentifier(node.id)) {
- return;
- }
+ if (!isVariableDeclarator(node.parent)) {
+ return;
+ }
- const references = context.sourceCode.getDeclaredVariables(node)[0]?.references ?? [];
- const usages = references.filter((ref) => !(ref.init ?? false));
- const effectSet = new Set();
+ if (!isIdentifier(node.parent.id)) {
+ return;
+ }
- for (const usage of usages) {
- const effect = AST.findParentNode(usage.identifier, (node) => isUseEffectLikeCall(node));
+ const references = sourceCode.getDeclaredVariables(node.parent)[0]?.references ?? [];
+ const usages = references.filter((ref) => !(ref.init ?? false));
+ const effectSet = new Set();
- if (effect == null) {
- return;
- }
+ for (const usage of usages) {
+ const effect = AST.findParentNode(usage.identifier, isUseEffectLikeCall);
- effectSet.add(effect);
- if (effectSet.size > 1) {
- return;
- }
- }
- context.report({
- messageId: "noUnnecessaryUseCallbackInsideUseEffect",
- node,
- data: { name: node.id.name },
- });
- },
+ if (effect == null) {
+ return;
+ }
+
+ effectSet.add(effect);
+ if (effectSet.size > 1) {
+ return;
+ }
+ }
+ return {
+ messageId: "noUnnecessaryUseCallbackInsideUseEffect",
+ node,
+ data: { name: node.parent.id.name },
};
}
diff --git a/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-memo.mdx b/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-memo.mdx
index 9a164deb1..69dda8bdc 100644
--- a/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-memo.mdx
+++ b/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-memo.mdx
@@ -1,81 +1,112 @@
----
-title: no-unnecessary-use-memo
----
-
-**Full Name in `@eslint-react/eslint-plugin`**
-
-```plain copy
-@eslint-react/no-unnecessary-use-memo
-```
-
-**Full Name in `eslint-plugin-react-x`**
-
-```plain copy
-react-x/no-unnecessary-use-memo
-```
-
-**Features**
-
-`🧪`
-
-**Presets**
-
-`strict`
-`strict-typescript`
-`strict-type-checked`
-
-## Description
-
-Disallow unnecessary usage of `useMemo`.
-
-React Hooks `useMemo` has empty dependencies array like what's in the examples, are unnecessary. The hook can be removed and it's value can be calculated in the component body or hoisted to the outer scope of the component.
-
-## Examples
-
-### Failing
-
-```tsx
-import { Button, MantineTheme } from "@mantine/core";
-import React, { useMemo } from "react";
-
-function MyComponent() {
- const style = useMemo(
- (theme: MantineTheme) => ({
- input: {
- fontFamily: theme.fontFamilyMonospace,
- },
- }),
- [],
- );
- return ;
-}
-```
-
-### Passing
-
-```tsx
-import { Button, MantineTheme } from "@mantine/core";
-import React from "react";
-
-const style = (theme: MantineTheme) => ({
- input: {
- fontFamily: theme.fontFamilyMonospace,
- },
-});
-
-function MyComponent() {
- return ;
-}
-```
-
-## Implementation
-
-- [Rule Source](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-memo.ts)
-- [Test Source](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-memo.spec.ts)
-
----
-
-## See Also
-
-- [`no-unnecessary-use-callback`](./no-unnecessary-use-callback)\
- Disallows unnecessary usage of `useCallback`.
+---
+title: no-unnecessary-use-memo
+---
+
+**Full Name in `@eslint-react/eslint-plugin`**
+
+```plain copy
+@eslint-react/no-unnecessary-use-memo
+```
+
+**Full Name in `eslint-plugin-react-x`**
+
+```plain copy
+react-x/no-unnecessary-use-memo
+```
+
+**Features**
+
+`🧪`
+
+**Presets**
+
+`strict`
+`strict-typescript`
+`strict-type-checked`
+
+## Description
+
+Disallow unnecessary usage of `useMemo`.
+
+React Hooks `useMemo` has empty dependencies array like what's in the examples, are unnecessary. The hook can be removed and it's value can be calculated in the component body or hoisted to the outer scope of the component.
+If the calculated variable is only used inside one useEffect the calculation can be moved inside the useEffect Function.
+
+## Examples
+
+### Failing
+
+```tsx
+import { Button, MantineTheme } from "@mantine/core";
+import React, { useMemo } from "react";
+
+function MyComponent() {
+ const style = useMemo(
+ (theme: MantineTheme) => ({
+ input: {
+ fontFamily: theme.fontFamilyMonospace,
+ },
+ }),
+ [],
+ );
+ return ;
+}
+```
+
+```tsx
+import { Button, MantineTheme } from "@mantine/core";
+import React, { useMemo, useEffect } from "react";
+
+function MyComponent({someNumbers}: {someNumbers: number[]}) {
+ const calculatedNumber = useMemo(
+ () => someNumbers.reduce((prev, next) => prev+next, 0),
+ [someNumbers],
+ );
+
+ useEffect(() => {
+ console.log(calculatedNumber)
+ }, [calculatedNumber])
+ return Hello World!
;
+}
+```
+
+### Passing
+
+```tsx
+import { Button, MantineTheme } from "@mantine/core";
+import React from "react";
+
+const style = (theme: MantineTheme) => ({
+ input: {
+ fontFamily: theme.fontFamilyMonospace,
+ },
+});
+
+function MyComponent() {
+ return ;
+}
+```
+
+```tsx
+import { Button, MantineTheme } from "@mantine/core";
+import React, { useEffect } from "react";
+
+function MyComponent({someNumbers}: {someNumbers: number[]}) {
+ useEffect(() => {
+ const calculatedNumber = someNumbers.reduce((prev, next) => prev+next, 0)
+ console.log(calculatedNumber)
+ }, [someNumbers])
+ return Hello World!
;
+}
+```
+
+## Implementation
+
+- [Rule Source](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-memo.ts)
+- [Test Source](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-memo.spec.ts)
+
+---
+
+## See Also
+
+- [`no-unnecessary-use-callback`](./no-unnecessary-use-callback)\
+ Disallows unnecessary usage of `useCallback`.
diff --git a/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-memo.ts b/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-memo.ts
index eca742b54..f57ec5eab 100644
--- a/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-memo.ts
+++ b/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-memo.ts
@@ -1,14 +1,14 @@
import * as AST from "@eslint-react/ast";
import { isUseEffectLikeCall, isUseMemoCall } from "@eslint-react/core";
import { identity } from "@eslint-react/eff";
-import type { RuleContext, RuleFeature } from "@eslint-react/shared";
+import { type RuleContext, type RuleFeature, report } from "@eslint-react/shared";
import { findVariable, getChildScopes, getVariableDefinitionNode } from "@eslint-react/var";
import { AST_NODE_TYPES as T, type TSESTree } from "@typescript-eslint/types";
-import type { RuleListener } from "@typescript-eslint/utils/ts-eslint";
+import type { ReportDescriptor, RuleListener, SourceCode } from "@typescript-eslint/utils/ts-eslint";
import type { CamelCase } from "string-ts";
import { match } from "ts-pattern";
-import { isIdentifier } from "@typescript-eslint/utils/ast-utils";
+import { isIdentifier, isVariableDeclarator } from "@typescript-eslint/utils/ast-utils";
import { createRule } from "../utils";
export const RULE_NAME = "no-unnecessary-use-memo";
@@ -47,6 +47,9 @@ export function create(context: RuleContext): RuleListener {
if (!isUseMemoCall(node)) {
return;
}
+
+ const checkForUsageInsideUseEffectReport = checkForUsageInsideUseEffect(context.sourceCode, node);
+
const scope = context.sourceCode.getScope(node);
const component = scope.block;
if (!AST.isFunction(component)) {
@@ -60,6 +63,7 @@ export function create(context: RuleContext): RuleListener {
&& [...AST.getNestedCallExpressions(arg0.body), ...AST.getNestedNewExpressions(arg0.body)].length > 0;
if (hasCallInArg0) {
+ report(context)(checkForUsageInsideUseEffectReport);
return;
}
@@ -76,6 +80,7 @@ export function create(context: RuleContext): RuleListener {
.otherwise(() => false);
if (!hasEmptyDeps) {
+ report(context)(checkForUsageInsideUseEffectReport);
return;
}
const arg0Node = match(arg0)
@@ -107,41 +112,45 @@ export function create(context: RuleContext): RuleListener {
node,
});
}
+ report(context)(checkForUsageInsideUseEffectReport);
},
- VariableDeclarator(node) {
- if (!context.sourceCode.text.includes("useEffect")) {
- return;
- }
+ };
+}
- if (!isUseMemoCall(node.init ?? undefined)) {
- return;
- }
+function checkForUsageInsideUseEffect(
+ sourceCode: Readonly,
+ node: TSESTree.CallExpression,
+): ReportDescriptor | undefined {
+ if (!/use\w*Effect/u.test(sourceCode.text)) return;
- if (!isIdentifier(node.id)) {
- return;
- }
+ if (!isVariableDeclarator(node.parent)) {
+ return;
+ }
- const references = context.sourceCode.getDeclaredVariables(node)[0]?.references ?? [];
- const usages = references.filter((ref) => !(ref.init ?? false));
- const effectSet = new Set();
+ if (!isIdentifier(node.parent.id)) {
+ return;
+ }
- for (const usage of usages) {
- const effect = AST.findParentNode(usage.identifier, (node) => isUseEffectLikeCall(node));
+ const references = sourceCode.getDeclaredVariables(node.parent)[0]?.references ?? [];
+ const usages = references.filter((ref) => !(ref.init ?? false));
+ const effectSet = new Set();
- if (effect == null) {
- return;
- }
+ for (const usage of usages) {
+ const effect = AST.findParentNode(usage.identifier, isUseEffectLikeCall);
- effectSet.add(effect);
- if (effectSet.size > 1) {
- return;
- }
- }
- context.report({
- messageId: "noUnnecessaryUseMemoInsideUseEffect",
- node,
- data: { name: node.id.name },
- });
- },
+ if (effect == null) {
+ return;
+ }
+
+ effectSet.add(effect);
+ if (effectSet.size > 1) {
+ return;
+ }
+ }
+
+ return {
+ messageId: "noUnnecessaryUseMemoInsideUseEffect",
+ node,
+ data: { name: node.parent.id.name },
};
}