Skip to content

Commit f788883

Browse files
update documentation for rules
fix both rules appearing at the same time. Now the original error message takes precedence
1 parent b5780e9 commit f788883

File tree

4 files changed

+223
-145
lines changed

4 files changed

+223
-145
lines changed

packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-callback.mdx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ react-x/no-unnecessary-use-callback
2929
Disallow unnecessary usage of `useCallback`.
3030

3131
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.
32+
If the calculated function is only used inside one useEffect the calculation can be moved inside the useEffect Function.
3233

3334
## Examples
3435

@@ -46,6 +47,22 @@ function MyComponent() {
4647
}
4748
```
4849

50+
51+
```tsx
52+
import { Button, MantineTheme } from "@mantine/core";
53+
import React, { useCallback, useEffect } from "react";
54+
55+
function MyComponent({items}: {items: string[]}) {
56+
const updateTest = useCallback(() => {console.log(items.length)}, [items]);
57+
58+
useEffect(() => {
59+
updateTest();
60+
}, [updateTest]);
61+
62+
return <div>Hello World</div>;
63+
}
64+
```
65+
4966
### Passing
5067

5168
```tsx
@@ -60,6 +77,19 @@ function MyComponent() {
6077
}
6178
```
6279

80+
```tsx
81+
import { Button, MantineTheme } from "@mantine/core";
82+
import React, { useEffect } from "react";
83+
84+
function MyComponent({items}: {items: string[]}) {
85+
useEffect(() => {
86+
console.log(items.length);
87+
}, [items]);
88+
89+
return <div>Hello World</div>;
90+
}
91+
```
92+
6393
## Implementation
6494

6595
- [Rule Source](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-callback.ts)

packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-callback.ts

Lines changed: 40 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import * as AST from "@eslint-react/ast";
22
import { isUseCallbackCall, isUseEffectLikeCall } from "@eslint-react/core";
33
import { identity } from "@eslint-react/eff";
4-
import type { RuleContext, RuleFeature } from "@eslint-react/shared";
4+
import { type RuleContext, type RuleFeature, report } from "@eslint-react/shared";
55
import { findVariable, getChildScopes, getVariableDefinitionNode } from "@eslint-react/var";
66
import type { TSESTree } from "@typescript-eslint/types";
77
import { AST_NODE_TYPES as T } from "@typescript-eslint/types";
8-
import { isIdentifier } from "@typescript-eslint/utils/ast-utils";
9-
import { type RuleListener } from "@typescript-eslint/utils/ts-eslint";
8+
import { isIdentifier, isVariableDeclarator } from "@typescript-eslint/utils/ast-utils";
9+
import { type ReportDescriptor, type RuleListener, type SourceCode } from "@typescript-eslint/utils/ts-eslint";
1010
import type { CamelCase } from "string-ts";
1111
import { match } from "ts-pattern";
1212
import { createRule } from "../utils";
@@ -49,6 +49,8 @@ export function create(context: RuleContext<MessageID, []>): RuleListener {
4949
return;
5050
}
5151

52+
const checkForUsageInsideUseEffectReport = checkForUsageInsideUseEffect(context.sourceCode, node);
53+
5254
const initialScope = context.sourceCode.getScope(node);
5355
const component = context.sourceCode.getScope(node).block;
5456

@@ -73,8 +75,10 @@ export function create(context: RuleContext<MessageID, []>): RuleListener {
7375
.otherwise(() => false);
7476

7577
if (!hasEmptyDeps) {
78+
report(context)(checkForUsageInsideUseEffectReport);
7679
return;
7780
}
81+
7882
const arg0Node = match(arg0)
7983
.with({ type: T.ArrowFunctionExpression }, (n) => {
8084
if (n.body.type === T.ArrowFunctionExpression) {
@@ -103,42 +107,46 @@ export function create(context: RuleContext<MessageID, []>): RuleListener {
103107
messageId: "noUnnecessaryUseCallback",
104108
node,
105109
});
106-
}
107-
},
108-
VariableDeclarator(node) {
109-
if (!context.sourceCode.text.includes("useEffect")) {
110110
return;
111111
}
112+
report(context)(checkForUsageInsideUseEffectReport);
113+
},
114+
};
115+
}
112116

113-
if (!isUseCallbackCall(node.init ?? undefined)) {
114-
return;
115-
}
117+
function checkForUsageInsideUseEffect(
118+
sourceCode: Readonly<SourceCode>,
119+
node: TSESTree.CallExpression,
120+
): ReportDescriptor<MessageID> | undefined {
121+
if (!/use\w*Effect/u.test(sourceCode.text)) return;
116122

117-
if (!isIdentifier(node.id)) {
118-
return;
119-
}
123+
if (!isVariableDeclarator(node.parent)) {
124+
return;
125+
}
120126

121-
const references = context.sourceCode.getDeclaredVariables(node)[0]?.references ?? [];
122-
const usages = references.filter((ref) => !(ref.init ?? false));
123-
const effectSet = new Set<TSESTree.Node>();
127+
if (!isIdentifier(node.parent.id)) {
128+
return;
129+
}
124130

125-
for (const usage of usages) {
126-
const effect = AST.findParentNode(usage.identifier, (node) => isUseEffectLikeCall(node));
131+
const references = sourceCode.getDeclaredVariables(node.parent)[0]?.references ?? [];
132+
const usages = references.filter((ref) => !(ref.init ?? false));
133+
const effectSet = new Set<TSESTree.Node>();
127134

128-
if (effect == null) {
129-
return;
130-
}
135+
for (const usage of usages) {
136+
const effect = AST.findParentNode(usage.identifier, isUseEffectLikeCall);
131137

132-
effectSet.add(effect);
133-
if (effectSet.size > 1) {
134-
return;
135-
}
136-
}
137-
context.report({
138-
messageId: "noUnnecessaryUseCallbackInsideUseEffect",
139-
node,
140-
data: { name: node.id.name },
141-
});
142-
},
138+
if (effect == null) {
139+
return;
140+
}
141+
142+
effectSet.add(effect);
143+
if (effectSet.size > 1) {
144+
return;
145+
}
146+
}
147+
return {
148+
messageId: "noUnnecessaryUseCallbackInsideUseEffect",
149+
node,
150+
data: { name: node.parent.id.name },
143151
};
144152
}
Lines changed: 112 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,81 +1,112 @@
1-
---
2-
title: no-unnecessary-use-memo
3-
---
4-
5-
**Full Name in `@eslint-react/eslint-plugin`**
6-
7-
```plain copy
8-
@eslint-react/no-unnecessary-use-memo
9-
```
10-
11-
**Full Name in `eslint-plugin-react-x`**
12-
13-
```plain copy
14-
react-x/no-unnecessary-use-memo
15-
```
16-
17-
**Features**
18-
19-
`🧪`
20-
21-
**Presets**
22-
23-
`strict`
24-
`strict-typescript`
25-
`strict-type-checked`
26-
27-
## Description
28-
29-
Disallow unnecessary usage of `useMemo`.
30-
31-
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.
32-
33-
## Examples
34-
35-
### Failing
36-
37-
```tsx
38-
import { Button, MantineTheme } from "@mantine/core";
39-
import React, { useMemo } from "react";
40-
41-
function MyComponent() {
42-
const style = useMemo(
43-
(theme: MantineTheme) => ({
44-
input: {
45-
fontFamily: theme.fontFamilyMonospace,
46-
},
47-
}),
48-
[],
49-
);
50-
return <Button sx={style} />;
51-
}
52-
```
53-
54-
### Passing
55-
56-
```tsx
57-
import { Button, MantineTheme } from "@mantine/core";
58-
import React from "react";
59-
60-
const style = (theme: MantineTheme) => ({
61-
input: {
62-
fontFamily: theme.fontFamilyMonospace,
63-
},
64-
});
65-
66-
function MyComponent() {
67-
return <Button sx={style} />;
68-
}
69-
```
70-
71-
## Implementation
72-
73-
- [Rule Source](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-memo.ts)
74-
- [Test Source](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-memo.spec.ts)
75-
76-
---
77-
78-
## See Also
79-
80-
- [`no-unnecessary-use-callback`](./no-unnecessary-use-callback)\
81-
Disallows unnecessary usage of `useCallback`.
1+
---
2+
title: no-unnecessary-use-memo
3+
---
4+
5+
**Full Name in `@eslint-react/eslint-plugin`**
6+
7+
```plain copy
8+
@eslint-react/no-unnecessary-use-memo
9+
```
10+
11+
**Full Name in `eslint-plugin-react-x`**
12+
13+
```plain copy
14+
react-x/no-unnecessary-use-memo
15+
```
16+
17+
**Features**
18+
19+
`🧪`
20+
21+
**Presets**
22+
23+
`strict`
24+
`strict-typescript`
25+
`strict-type-checked`
26+
27+
## Description
28+
29+
Disallow unnecessary usage of `useMemo`.
30+
31+
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.
32+
If the calculated variable is only used inside one useEffect the calculation can be moved inside the useEffect Function.
33+
34+
## Examples
35+
36+
### Failing
37+
38+
```tsx
39+
import { Button, MantineTheme } from "@mantine/core";
40+
import React, { useMemo } from "react";
41+
42+
function MyComponent() {
43+
const style = useMemo(
44+
(theme: MantineTheme) => ({
45+
input: {
46+
fontFamily: theme.fontFamilyMonospace,
47+
},
48+
}),
49+
[],
50+
);
51+
return <Button sx={style} />;
52+
}
53+
```
54+
55+
```tsx
56+
import { Button, MantineTheme } from "@mantine/core";
57+
import React, { useMemo, useEffect } from "react";
58+
59+
function MyComponent({someNumbers}: {someNumbers: number[]}) {
60+
const calculatedNumber = useMemo(
61+
() => someNumbers.reduce((prev, next) => prev+next, 0),
62+
[someNumbers],
63+
);
64+
65+
useEffect(() => {
66+
console.log(calculatedNumber)
67+
}, [calculatedNumber])
68+
return <div> Hello World! </div>;
69+
}
70+
```
71+
72+
### Passing
73+
74+
```tsx
75+
import { Button, MantineTheme } from "@mantine/core";
76+
import React from "react";
77+
78+
const style = (theme: MantineTheme) => ({
79+
input: {
80+
fontFamily: theme.fontFamilyMonospace,
81+
},
82+
});
83+
84+
function MyComponent() {
85+
return <Button sx={style} />;
86+
}
87+
```
88+
89+
```tsx
90+
import { Button, MantineTheme } from "@mantine/core";
91+
import React, { useEffect } from "react";
92+
93+
function MyComponent({someNumbers}: {someNumbers: number[]}) {
94+
useEffect(() => {
95+
const calculatedNumber = someNumbers.reduce((prev, next) => prev+next, 0)
96+
console.log(calculatedNumber)
97+
}, [someNumbers])
98+
return <div> Hello World! </div>;
99+
}
100+
```
101+
102+
## Implementation
103+
104+
- [Rule Source](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-memo.ts)
105+
- [Test Source](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-memo.spec.ts)
106+
107+
---
108+
109+
## See Also
110+
111+
- [`no-unnecessary-use-callback`](./no-unnecessary-use-callback)\
112+
Disallows unnecessary usage of `useCallback`.

0 commit comments

Comments
 (0)