Skip to content

Commit cf1cbe4

Browse files
committed
[eslint-plugin-react-hooks] add option experimental_effectHooksWithNullDependenciesAllowed
1 parent 4448b18 commit cf1cbe4

File tree

2 files changed

+82
-7
lines changed

2 files changed

+82
-7
lines changed

packages/eslint-plugin-react-hooks/__tests__/ESLintRuleExhaustiveDeps-test.js

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1468,8 +1468,57 @@ const tests = {
14681468
}
14691469
`,
14701470
},
1471+
{
1472+
code: normalizeIndent`
1473+
function MyComponent(props) {
1474+
useSpecialEffect(() => {
1475+
console.log(props.foo);
1476+
}, null);
1477+
}
1478+
`,
1479+
options: [
1480+
{
1481+
additionalHooks: 'useSpecialEffect',
1482+
experimental_effectHooksWithNullDependenciesAllowed: [
1483+
'useSpecialEffect',
1484+
],
1485+
},
1486+
],
1487+
},
14711488
],
14721489
invalid: [
1490+
{
1491+
code: normalizeIndent`
1492+
function MyComponent(props) {
1493+
useSpecialEffect(() => {
1494+
console.log(props.foo);
1495+
}, null);
1496+
}
1497+
`,
1498+
options: [{additionalHooks: 'useSpecialEffect'}],
1499+
errors: [
1500+
{
1501+
message:
1502+
"React Hook useSpecialEffect was passed a dependency list that is not an array literal. This means we can't statically verify whether you've passed the correct dependencies.",
1503+
},
1504+
{
1505+
message:
1506+
"React Hook useSpecialEffect has a missing dependency: 'props.foo'. Either include it or remove the dependency array.",
1507+
suggestions: [
1508+
{
1509+
desc: 'Update the dependencies array to be: [props.foo]',
1510+
output: normalizeIndent`
1511+
function MyComponent(props) {
1512+
useSpecialEffect(() => {
1513+
console.log(props.foo);
1514+
}, [props.foo]);
1515+
}
1516+
`,
1517+
},
1518+
],
1519+
},
1520+
],
1521+
},
14731522
{
14741523
code: normalizeIndent`
14751524
function MyComponent(props) {

packages/eslint-plugin-react-hooks/src/rules/ExhaustiveDeps.ts

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -61,27 +61,41 @@ const rule = {
6161
enableDangerousAutofixThisMayCauseInfiniteLoops: {
6262
type: 'boolean',
6363
},
64+
experimental_effectHooksWithNullDependenciesAllowed: {
65+
type: 'array',
66+
items: {
67+
type: 'string',
68+
},
69+
},
6470
},
6571
},
6672
],
6773
},
6874
create(context: Rule.RuleContext) {
75+
const rawOptions = context.options && context.options[0];
76+
6977
// Parse the `additionalHooks` regex.
7078
const additionalHooks =
71-
context.options &&
72-
context.options[0] &&
73-
context.options[0].additionalHooks
74-
? new RegExp(context.options[0].additionalHooks)
79+
rawOptions && rawOptions.additionalHooks
80+
? new RegExp(rawOptions.additionalHooks)
7581
: undefined;
7682

7783
const enableDangerousAutofixThisMayCauseInfiniteLoops: boolean =
78-
(context.options &&
79-
context.options[0] &&
80-
context.options[0].enableDangerousAutofixThisMayCauseInfiniteLoops) ||
84+
(rawOptions &&
85+
rawOptions.enableDangerousAutofixThisMayCauseInfiniteLoops) ||
8186
false;
8287

88+
const experimental_effectHooksWithNullDependenciesAllowed: ReadonlyArray<string> =
89+
rawOptions &&
90+
Array.isArray(
91+
rawOptions.experimental_effectHooksWithNullDependenciesAllowed,
92+
)
93+
? rawOptions.experimental_effectHooksWithNullDependenciesAllowed
94+
: [];
95+
8396
const options = {
8497
additionalHooks,
98+
experimental_effectHooksWithNullDependenciesAllowed,
8599
enableDangerousAutofixThisMayCauseInfiniteLoops,
86100
};
87101

@@ -1318,6 +1332,18 @@ const rule = {
13181332
return;
13191333
}
13201334

1335+
// special case effects which allows null as the equivalent of useEffect with no array.
1336+
if (
1337+
!declaredDependenciesNode ||
1338+
(options.experimental_effectHooksWithNullDependenciesAllowed.includes(
1339+
reactiveHookName,
1340+
) &&
1341+
declaredDependenciesNode.type === 'Literal' &&
1342+
declaredDependenciesNode.value === null)
1343+
) {
1344+
return;
1345+
}
1346+
13211347
// Check the declared dependencies for this reactive hook. If there is no
13221348
// second argument then the reactive callback will re-run on every render.
13231349
// So no need to check for dependency inclusion.

0 commit comments

Comments
 (0)