Skip to content

Commit eb6247a

Browse files
authored
More concise messages (#15053)
1 parent 197703e commit eb6247a

File tree

2 files changed

+80
-79
lines changed

2 files changed

+80
-79
lines changed

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

+59-53
Original file line numberDiff line numberDiff line change
@@ -1236,7 +1236,7 @@ const tests = {
12361236
errors: [
12371237
"React Hook useCallback has a missing dependency: 'local2'. " +
12381238
'Either include it or remove the dependency array. ' +
1239-
"Values like 'local1' aren't valid dependencies " +
1239+
"Outer scope values like 'local1' aren't valid dependencies " +
12401240
"because their mutation doesn't re-render the component.",
12411241
],
12421242
},
@@ -1302,7 +1302,7 @@ const tests = {
13021302
errors: [
13031303
"React Hook useCallback has an unnecessary dependency: 'window'. " +
13041304
'Either exclude it or remove the dependency array. ' +
1305-
"Values like 'window' aren't valid dependencies " +
1305+
"Outer scope values like 'window' aren't valid dependencies " +
13061306
"because their mutation doesn't re-render the component.",
13071307
],
13081308
},
@@ -2304,9 +2304,8 @@ const tests = {
23042304
errors: [
23052305
"React Hook useEffect has a missing dependency: 'state'. " +
23062306
'Either include it or remove the dependency array. ' +
2307-
`If 'state' is only necessary for calculating the next state, ` +
2308-
`consider refactoring to the setState(state => ...) form which ` +
2309-
`doesn't need to depend on the state from outside.`,
2307+
`You can also write 'setState(state => ...)' ` +
2308+
`if you only use 'state' for the 'setState' call.`,
23102309
],
23112310
},
23122311
{
@@ -2336,9 +2335,8 @@ const tests = {
23362335
errors: [
23372336
"React Hook useEffect has a missing dependency: 'state'. " +
23382337
'Either include it or remove the dependency array. ' +
2339-
`If 'state' is only necessary for calculating the next state, ` +
2340-
`consider refactoring to the setState(state => ...) form which ` +
2341-
`doesn't need to depend on the state from outside.`,
2338+
`You can also write 'setState(state => ...)' ` +
2339+
`if you only use 'state' for the 'setState' call.`,
23422340
],
23432341
},
23442342
{
@@ -3066,7 +3064,7 @@ const tests = {
30663064
errors: [
30673065
"React Hook useEffect has an unnecessary dependency: 'window'. " +
30683066
'Either exclude it or remove the dependency array. ' +
3069-
"Values like 'window' aren't valid dependencies " +
3067+
"Outer scope values like 'window' aren't valid dependencies " +
30703068
"because their mutation doesn't re-render the component.",
30713069
],
30723070
},
@@ -3092,7 +3090,7 @@ const tests = {
30923090
errors: [
30933091
"React Hook useEffect has an unnecessary dependency: 'MutableStore.hello'. " +
30943092
'Either exclude it or remove the dependency array. ' +
3095-
"Values like 'MutableStore.hello' aren't valid dependencies " +
3093+
"Outer scope values like 'MutableStore.hello' aren't valid dependencies " +
30963094
"because their mutation doesn't re-render the component.",
30973095
],
30983096
},
@@ -3129,7 +3127,7 @@ const tests = {
31293127
'React Hook useEffect has unnecessary dependencies: ' +
31303128
"'MutableStore.hello.world', 'global.stuff', and 'z'. " +
31313129
'Either exclude them or remove the dependency array. ' +
3132-
"Values like 'MutableStore.hello.world' aren't valid dependencies " +
3130+
"Outer scope values like 'MutableStore.hello.world' aren't valid dependencies " +
31333131
"because their mutation doesn't re-render the component.",
31343132
],
31353133
},
@@ -3168,7 +3166,7 @@ const tests = {
31683166
'React Hook useEffect has unnecessary dependencies: ' +
31693167
"'MutableStore.hello.world', 'global.stuff', and 'z'. " +
31703168
'Either exclude them or remove the dependency array. ' +
3171-
"Values like 'MutableStore.hello.world' aren't valid dependencies " +
3169+
"Outer scope values like 'MutableStore.hello.world' aren't valid dependencies " +
31723170
"because their mutation doesn't re-render the component.",
31733171
],
31743172
},
@@ -3205,7 +3203,7 @@ const tests = {
32053203
'React Hook useCallback has unnecessary dependencies: ' +
32063204
"'MutableStore.hello.world', 'global.stuff', 'props.foo', 'x', 'y', and 'z'. " +
32073205
'Either exclude them or remove the dependency array. ' +
3208-
"Values like 'MutableStore.hello.world' aren't valid dependencies " +
3206+
"Outer scope values like 'MutableStore.hello.world' aren't valid dependencies " +
32093207
"because their mutation doesn't re-render the component.",
32103208
],
32113209
},
@@ -3468,8 +3466,7 @@ const tests = {
34683466
errors: [
34693467
`The 'handleNext' function makes the dependencies of ` +
34703468
`useEffect Hook (at line 11) change on every render. ` +
3471-
`To fix this, move the 'handleNext' function ` +
3472-
`inside the useEffect callback (at line 9). Alternatively, ` +
3469+
`Move it inside the useEffect callback. Alternatively, ` +
34733470
`wrap the 'handleNext' definition into its own useCallback() Hook.`,
34743471
],
34753472
},
@@ -3507,8 +3504,7 @@ const tests = {
35073504
errors: [
35083505
`The 'handleNext' function makes the dependencies of ` +
35093506
`useEffect Hook (at line 11) change on every render. ` +
3510-
`To fix this, move the 'handleNext' function ` +
3511-
`inside the useEffect callback (at line 9). Alternatively, ` +
3507+
`Move it inside the useEffect callback. Alternatively, ` +
35123508
`wrap the 'handleNext' definition into its own useCallback() Hook.`,
35133509
],
35143510
},
@@ -3605,17 +3601,14 @@ const tests = {
36053601
`,
36063602
errors: [
36073603
"The 'handleNext1' function makes the dependencies of useEffect Hook " +
3608-
"(at line 14) change on every render. To fix this, move the 'handleNext1' " +
3609-
'function inside the useEffect callback (at line 12). Alternatively, wrap the ' +
3610-
"'handleNext1' definition into its own useCallback() Hook.",
3604+
'(at line 14) change on every render. Move it inside the useEffect callback. ' +
3605+
"Alternatively, wrap the 'handleNext1' definition into its own useCallback() Hook.",
36113606
"The 'handleNext2' function makes the dependencies of useLayoutEffect Hook " +
3612-
"(at line 17) change on every render. To fix this, move the 'handleNext2' " +
3613-
'function inside the useLayoutEffect callback (at line 15). Alternatively, wrap the ' +
3614-
"'handleNext2' definition into its own useCallback() Hook.",
3607+
'(at line 17) change on every render. Move it inside the useLayoutEffect callback. ' +
3608+
"Alternatively, wrap the 'handleNext2' definition into its own useCallback() Hook.",
36153609
"The 'handleNext3' function makes the dependencies of useMemo Hook " +
3616-
"(at line 20) change on every render. To fix this, move the 'handleNext3' " +
3617-
'function inside the useMemo callback (at line 18). Alternatively, wrap the ' +
3618-
"'handleNext3' definition into its own useCallback() Hook.",
3610+
'(at line 20) change on every render. Move it inside the useMemo callback. ' +
3611+
"Alternatively, wrap the 'handleNext3' definition into its own useCallback() Hook.",
36193612
],
36203613
},
36213614
{
@@ -3673,17 +3666,14 @@ const tests = {
36733666
`,
36743667
errors: [
36753668
"The 'handleNext1' function makes the dependencies of useEffect Hook " +
3676-
"(at line 15) change on every render. To fix this, move the 'handleNext1' " +
3677-
'function inside the useEffect callback (at line 12). Alternatively, wrap the ' +
3678-
"'handleNext1' definition into its own useCallback() Hook.",
3669+
'(at line 15) change on every render. Move it inside the useEffect callback. ' +
3670+
"Alternatively, wrap the 'handleNext1' definition into its own useCallback() Hook.",
36793671
"The 'handleNext2' function makes the dependencies of useLayoutEffect Hook " +
3680-
"(at line 19) change on every render. To fix this, move the 'handleNext2' " +
3681-
'function inside the useLayoutEffect callback (at line 16). Alternatively, wrap the ' +
3682-
"'handleNext2' definition into its own useCallback() Hook.",
3672+
'(at line 19) change on every render. Move it inside the useLayoutEffect callback. ' +
3673+
"Alternatively, wrap the 'handleNext2' definition into its own useCallback() Hook.",
36833674
"The 'handleNext3' function makes the dependencies of useMemo Hook " +
3684-
"(at line 23) change on every render. To fix this, move the 'handleNext3' " +
3685-
'function inside the useMemo callback (at line 20). Alternatively, wrap the ' +
3686-
"'handleNext3' definition into its own useCallback() Hook.",
3675+
'(at line 23) change on every render. Move it inside the useMemo callback. ' +
3676+
"Alternatively, wrap the 'handleNext3' definition into its own useCallback() Hook.",
36873677
],
36883678
},
36893679
{
@@ -3907,8 +3897,7 @@ const tests = {
39073897
errors: [
39083898
`The 'handleNext' function makes the dependencies of ` +
39093899
`useEffect Hook (at line 14) change on every render. ` +
3910-
`To fix this, move the 'handleNext' function inside ` +
3911-
`the useEffect callback (at line 12). Alternatively, wrap the ` +
3900+
`Move it inside the useEffect callback. Alternatively, wrap the ` +
39123901
`'handleNext' definition into its own useCallback() Hook.`,
39133902
],
39143903
},
@@ -3944,9 +3933,8 @@ const tests = {
39443933
errors: [
39453934
"React Hook useEffect has a missing dependency: 'count'. " +
39463935
'Either include it or remove the dependency array. ' +
3947-
`If 'count' is only necessary for calculating the next state, ` +
3948-
`consider refactoring to the setCount(count => ...) form which ` +
3949-
`doesn't need to depend on the state from outside.`,
3936+
`You can also write 'setCount(count => ...)' if you ` +
3937+
`only use 'count' for the 'setCount' call.`,
39503938
],
39513939
},
39523940
{
@@ -3983,9 +3971,8 @@ const tests = {
39833971
errors: [
39843972
"React Hook useEffect has missing dependencies: 'count' and 'increment'. " +
39853973
'Either include them or remove the dependency array. ' +
3986-
`If 'count' is only necessary for calculating the next state, ` +
3987-
`consider refactoring to the setCount(count => ...) form which ` +
3988-
`doesn't need to depend on the state from outside.`,
3974+
`You can also write 'setCount(count => ...)' if you ` +
3975+
`only use 'count' for the 'setCount' call.`,
39893976
],
39903977
},
39913978
{
@@ -4022,9 +4009,8 @@ const tests = {
40224009
errors: [
40234010
"React Hook useEffect has a missing dependency: 'increment'. " +
40244011
'Either include it or remove the dependency array. ' +
4025-
`If 'increment' is only necessary for calculating the next state, ` +
4026-
`consider refactoring to the useReducer Hook. This ` +
4027-
`lets you move the calculation of next state outside the effect.`,
4012+
`You can also replace multiple useState variables with useReducer ` +
4013+
`if 'setCount' needs the current value of 'increment'.`,
40284014
],
40294015
},
40304016
{
@@ -4150,8 +4136,7 @@ const tests = {
41504136
`,
41514137
errors: [
41524138
`The 'increment' function makes the dependencies of useEffect Hook ` +
4153-
`(at line 14) change on every render. To fix this, move the ` +
4154-
`'increment' function inside the useEffect callback (at line 9). ` +
4139+
`(at line 14) change on every render. Move it inside the useEffect callback. ` +
41554140
`Alternatively, wrap the \'increment\' definition into its own ` +
41564141
`useCallback() Hook.`,
41574142
],
@@ -4188,11 +4173,8 @@ const tests = {
41884173
errors: [
41894174
"React Hook useEffect has a missing dependency: 'increment'. " +
41904175
'Either include it or remove the dependency array. ' +
4191-
`If 'increment' is only necessary for calculating the next state, ` +
4192-
`consider refactoring to the useReducer Hook. This lets you move ` +
4193-
`the calculation of next state outside the effect. ` +
4194-
`You can then read 'increment' from the reducer ` +
4195-
`by putting it directly in your component.`,
4176+
'You can also replace useState with an inline useReducer ' +
4177+
`if 'setCount' needs the current value of 'increment'.`,
41964178
],
41974179
},
41984180
{
@@ -4373,6 +4355,30 @@ const tests = {
43734355
`find the parent component that defines it and wrap that definition in useCallback.`,
43744356
],
43754357
},
4358+
{
4359+
// The mistake here is that it was moved inside the effect
4360+
// so it can't be referenced in the deps array.
4361+
code: `
4362+
function Thing() {
4363+
useEffect(() => {
4364+
const fetchData = async () => {};
4365+
fetchData();
4366+
}, [fetchData]);
4367+
}
4368+
`,
4369+
output: `
4370+
function Thing() {
4371+
useEffect(() => {
4372+
const fetchData = async () => {};
4373+
fetchData();
4374+
}, []);
4375+
}
4376+
`,
4377+
errors: [
4378+
`React Hook useEffect has an unnecessary dependency: 'fetchData'. ` +
4379+
`Either exclude it or remove the dependency array.`,
4380+
],
4381+
},
43764382
],
43774383
};
43784384

packages/eslint-plugin-react-hooks/src/ExhaustiveDeps.js

+21-26
Original file line numberDiff line numberDiff line change
@@ -617,10 +617,7 @@ export default {
617617
}' definition into its own useCallback() Hook.`;
618618
} else {
619619
message +=
620-
` To fix this, move the '${fn.name.name}' function ` +
621-
`inside the ${reactiveHookName} callback (at line ${
622-
node.loc.start.line
623-
}). ` +
620+
` Move it inside the ${reactiveHookName} callback. ` +
624621
`Alternatively, wrap the '${
625622
fn.name.name
626623
}' definition into its own useCallback() Hook.`;
@@ -715,9 +712,13 @@ export default {
715712
"because their mutation doesn't re-render the component.";
716713
} else if (externalDependencies.size > 0) {
717714
const dep = Array.from(externalDependencies)[0];
718-
extraWarning =
719-
` Values like '${dep}' aren't valid dependencies ` +
720-
`because their mutation doesn't re-render the component.`;
715+
// Don't show this warning for things that likely just got moved *inside* the callback
716+
// because in that case they're clearly not referring to globals.
717+
if (!scope.set.has(dep)) {
718+
extraWarning =
719+
` Outer scope values like '${dep}' aren't valid dependencies ` +
720+
`because their mutation doesn't re-render the component.`;
721+
}
721722
}
722723
}
723724

@@ -874,36 +875,30 @@ export default {
874875
}
875876
});
876877
if (setStateRecommendation !== null) {
877-
let suggestion;
878878
switch (setStateRecommendation.form) {
879879
case 'reducer':
880-
suggestion =
881-
'useReducer Hook. This lets you move the calculation ' +
882-
'of next state outside the effect.';
880+
extraWarning =
881+
` You can also replace multiple useState variables with useReducer ` +
882+
`if '${setStateRecommendation.setter}' needs the ` +
883+
`current value of '${setStateRecommendation.missingDep}'.`;
883884
break;
884885
case 'inlineReducer':
885-
suggestion =
886-
'useReducer Hook. This lets you move the calculation ' +
887-
'of next state outside the effect. You can then ' +
888-
`read '${
889-
setStateRecommendation.missingDep
890-
}' from the reducer ` +
891-
`by putting it directly in your component.`;
886+
extraWarning =
887+
` You can also replace useState with an inline useReducer ` +
888+
`if '${setStateRecommendation.setter}' needs the ` +
889+
`current value of '${setStateRecommendation.missingDep}'.`;
892890
break;
893891
case 'updater':
894-
suggestion =
895-
`${setStateRecommendation.setter}(${
892+
extraWarning =
893+
` You can also write '${setStateRecommendation.setter}(${
896894
setStateRecommendation.missingDep
897-
} => ...) form ` +
898-
`which doesn't need to depend on the state from outside.`;
895+
} => ...)' if you only use '${
896+
setStateRecommendation.missingDep
897+
}'` + ` for the '${setStateRecommendation.setter}' call.`;
899898
break;
900899
default:
901900
throw new Error('Unknown case.');
902901
}
903-
extraWarning =
904-
` If '${setStateRecommendation.missingDep}'` +
905-
` is only necessary for calculating the next state, ` +
906-
`consider refactoring to the ${suggestion}`;
907902
}
908903
}
909904

0 commit comments

Comments
 (0)