Skip to content

Commit 3ada82b

Browse files
authored
Allow extraneous effect dependencies (#14967)
This makes cases like useEffect(() => { window.scrollTo(0, 0); }, [activeTab]); not warn. However, it still warns for unused useCallback/useMemo deps.
1 parent 00748c5 commit 3ada82b

File tree

2 files changed

+99
-22
lines changed

2 files changed

+99
-22
lines changed

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

+87-20
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ const tests = {
9595
const local1 = {};
9696
{
9797
const local2 = {};
98-
useEffect(() => {
98+
useCallback(() => {
9999
console.log(local1);
100100
console.log(local2);
101101
}, [local1, local2]);
@@ -109,7 +109,7 @@ const tests = {
109109
const local1 = {};
110110
function MyNestedComponent() {
111111
const local2 = {};
112-
useEffect(() => {
112+
useCallback(() => {
113113
console.log(local1);
114114
console.log(local2);
115115
}, [local2]);
@@ -590,6 +590,17 @@ const tests = {
590590
}
591591
`,
592592
},
593+
{
594+
// Valid even though activeTab is "unused".
595+
// We allow that in effects, but not callbacks or memo.
596+
code: `
597+
function Foo({ activeTab }) {
598+
useEffect(() => {
599+
window.scrollTo(0, 0);
600+
}, [activeTab]);
601+
}
602+
`,
603+
},
593604
],
594605
invalid: [
595606
{
@@ -809,7 +820,7 @@ const tests = {
809820
function MyComponent() {
810821
const local1 = {};
811822
const local2 = {};
812-
useEffect(() => {
823+
useMemo(() => {
813824
console.log(local1);
814825
}, [local1, local2]);
815826
}
@@ -818,13 +829,13 @@ const tests = {
818829
function MyComponent() {
819830
const local1 = {};
820831
const local2 = {};
821-
useEffect(() => {
832+
useMemo(() => {
822833
console.log(local1);
823834
}, [local1]);
824835
}
825836
`,
826837
errors: [
827-
"React Hook useEffect has an unnecessary dependency: 'local2'. " +
838+
"React Hook useMemo has an unnecessary dependency: 'local2'. " +
828839
'Either exclude it or remove the dependency array.',
829840
],
830841
},
@@ -836,7 +847,7 @@ const tests = {
836847
const local1 = {};
837848
function MyNestedComponent() {
838849
const local2 = {};
839-
useEffect(() => {
850+
useCallback(() => {
840851
console.log(local1);
841852
console.log(local2);
842853
}, [local1]);
@@ -848,7 +859,7 @@ const tests = {
848859
const local1 = {};
849860
function MyNestedComponent() {
850861
const local2 = {};
851-
useEffect(() => {
862+
useCallback(() => {
852863
console.log(local1);
853864
console.log(local2);
854865
}, [local2]);
@@ -857,7 +868,7 @@ const tests = {
857868
`,
858869
errors: [
859870
// Focus on the more important part first (missing dep)
860-
"React Hook useEffect has a missing dependency: 'local2'. " +
871+
"React Hook useCallback has a missing dependency: 'local2'. " +
861872
'Either include it or remove the dependency array.',
862873
],
863874
},
@@ -912,16 +923,16 @@ const tests = {
912923
{
913924
code: `
914925
function MyComponent() {
915-
useEffect(() => {}, [local]);
926+
useCallback(() => {}, [local]);
916927
}
917928
`,
918929
output: `
919930
function MyComponent() {
920-
useEffect(() => {}, []);
931+
useCallback(() => {}, []);
921932
}
922933
`,
923934
errors: [
924-
"React Hook useEffect has an unnecessary dependency: 'local'. " +
935+
"React Hook useCallback has an unnecessary dependency: 'local'. " +
925936
'Either exclude it or remove the dependency array.',
926937
],
927938
},
@@ -1229,7 +1240,6 @@ const tests = {
12291240
],
12301241
},
12311242
{
1232-
// TODO: need to think more about this case.
12331243
code: `
12341244
function MyComponent() {
12351245
const local = {id: 42};
@@ -1238,13 +1248,14 @@ const tests = {
12381248
}, [local.id]);
12391249
}
12401250
`,
1241-
// TODO: this may not be a good idea.
1251+
// TODO: autofix should be smart enough
1252+
// to remove local.id from the list.
12421253
output: `
12431254
function MyComponent() {
12441255
const local = {id: 42};
12451256
useEffect(() => {
12461257
console.log(local);
1247-
}, [local]);
1258+
}, [local, local.id]);
12481259
}
12491260
`,
12501261
errors: [
@@ -1278,7 +1289,7 @@ const tests = {
12781289
code: `
12791290
function MyComponent() {
12801291
const local1 = {};
1281-
useEffect(() => {
1292+
useCallback(() => {
12821293
const local1 = {};
12831294
console.log(local1);
12841295
}, [local1]);
@@ -1287,32 +1298,32 @@ const tests = {
12871298
output: `
12881299
function MyComponent() {
12891300
const local1 = {};
1290-
useEffect(() => {
1301+
useCallback(() => {
12911302
const local1 = {};
12921303
console.log(local1);
12931304
}, []);
12941305
}
12951306
`,
12961307
errors: [
1297-
"React Hook useEffect has an unnecessary dependency: 'local1'. " +
1308+
"React Hook useCallback has an unnecessary dependency: 'local1'. " +
12981309
'Either exclude it or remove the dependency array.',
12991310
],
13001311
},
13011312
{
13021313
code: `
13031314
function MyComponent() {
13041315
const local1 = {};
1305-
useEffect(() => {}, [local1]);
1316+
useCallback(() => {}, [local1]);
13061317
}
13071318
`,
13081319
output: `
13091320
function MyComponent() {
13101321
const local1 = {};
1311-
useEffect(() => {}, []);
1322+
useCallback(() => {}, []);
13121323
}
13131324
`,
13141325
errors: [
1315-
"React Hook useEffect has an unnecessary dependency: 'local1'. " +
1326+
"React Hook useCallback has an unnecessary dependency: 'local1'. " +
13161327
'Either exclude it or remove the dependency array.',
13171328
],
13181329
},
@@ -1785,6 +1796,62 @@ const tests = {
17851796
"because their mutation doesn't re-render the component.",
17861797
],
17871798
},
1799+
{
1800+
code: `
1801+
function MyComponent({ activeTab }) {
1802+
const ref1 = useRef();
1803+
const ref2 = useRef();
1804+
useEffect(() => {
1805+
ref1.current.scrollTop = 0;
1806+
ref2.current.scrollTop = 0;
1807+
}, [ref1.current, ref2.current, activeTab]);
1808+
}
1809+
`,
1810+
output: `
1811+
function MyComponent({ activeTab }) {
1812+
const ref1 = useRef();
1813+
const ref2 = useRef();
1814+
useEffect(() => {
1815+
ref1.current.scrollTop = 0;
1816+
ref2.current.scrollTop = 0;
1817+
}, [activeTab]);
1818+
}
1819+
`,
1820+
errors: [
1821+
"React Hook useEffect has unnecessary dependencies: 'ref1.current' and 'ref2.current'. " +
1822+
'Either exclude them or remove the dependency array. ' +
1823+
"Mutable values like 'ref1.current' aren't valid dependencies " +
1824+
"because their mutation doesn't re-render the component.",
1825+
],
1826+
},
1827+
{
1828+
code: `
1829+
function MyComponent({ activeTab, initY }) {
1830+
const ref1 = useRef();
1831+
const ref2 = useRef();
1832+
const fn = useCallback(() => {
1833+
ref1.current.scrollTop = initY;
1834+
ref2.current.scrollTop = initY;
1835+
}, [ref1.current, ref2.current, activeTab, initY]);
1836+
}
1837+
`,
1838+
output: `
1839+
function MyComponent({ activeTab, initY }) {
1840+
const ref1 = useRef();
1841+
const ref2 = useRef();
1842+
const fn = useCallback(() => {
1843+
ref1.current.scrollTop = initY;
1844+
ref2.current.scrollTop = initY;
1845+
}, [initY]);
1846+
}
1847+
`,
1848+
errors: [
1849+
"React Hook useCallback has unnecessary dependencies: 'activeTab', 'ref1.current', and 'ref2.current'. " +
1850+
'Either exclude them or remove the dependency array. ' +
1851+
"Mutable values like 'ref1.current' aren't valid dependencies " +
1852+
"because their mutation doesn't re-render the component.",
1853+
],
1854+
},
17881855
{
17891856
code: `
17901857
function MyComponent() {

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

+12-2
Original file line numberDiff line numberDiff line change
@@ -330,8 +330,18 @@ export default {
330330
duplicateDependencies.add(key);
331331
}
332332
} else {
333-
// Unnecessary dependency. Do nothing.
334-
unnecessaryDependencies.add(key);
333+
if (isEffect && !key.endsWith('.current')) {
334+
// Effects may have extra "unnecessary" deps.
335+
// Such as resetting scroll when ID changes.
336+
// The exception is ref.current which is always wrong.
337+
// Consider them legit.
338+
if (suggestedDependencies.indexOf(key) === -1) {
339+
suggestedDependencies.push(key);
340+
}
341+
} else {
342+
// Unnecessary dependency. Remember to report it.
343+
unnecessaryDependencies.add(key);
344+
}
335345
}
336346
});
337347

0 commit comments

Comments
 (0)