main
js 8,781 lines 260 KB
Raw
1 /**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 *
7 * @jest-environment node
8 */
9
10 'use strict';
11
12 const ESLintTesterV7 = require('eslint-v7').RuleTester;
13 const ESLintTesterV9 = require('eslint-v9').RuleTester;
14 const ReactHooksESLintPlugin = require('eslint-plugin-react-hooks');
15 const ReactHooksESLintRule =
16 ReactHooksESLintPlugin.default.rules['exhaustive-deps'];
17
18 /**
19 * A string template tag that removes padding from the left side of multi-line strings
20 * @param {Array} strings array of code strings (only one expected)
21 */
22 function normalizeIndent(strings) {
23 const codeLines = strings[0].split('\n');
24 const leftPadding = codeLines[1].match(/\s+/)[0];
25 return codeLines.map(line => line.slice(leftPadding.length)).join('\n');
26 }
27
28 // ***************************************************
29 // For easier local testing, you can add to any case:
30 // {
31 // skip: true,
32 // --or--
33 // only: true,
34 // ...
35 // }
36 // ***************************************************
37
38 // Tests that are valid/invalid across all parsers
39 const tests = {
40 valid: [
41 {
42 code: normalizeIndent`
43 function MyComponent() {
44 const local = {};
45 useEffect(() => {
46 console.log(local);
47 });
48 }
49 `,
50 },
51 {
52 code: normalizeIndent`
53 function MyComponent() {
54 useEffect(() => {
55 const local = {};
56 console.log(local);
57 }, []);
58 }
59 `,
60 },
61 {
62 code: normalizeIndent`
63 function MyComponent() {
64 const local = someFunc();
65 useEffect(() => {
66 console.log(local);
67 }, [local]);
68 }
69 `,
70 },
71 {
72 // OK because `props` wasn't defined.
73 // We don't technically know if `props` is supposed
74 // to be an import that hasn't been added yet, or
75 // a component-level variable. Ignore it until it
76 // gets defined (a different rule would flag it anyway).
77 code: normalizeIndent`
78 function MyComponent() {
79 useEffect(() => {
80 console.log(props.foo);
81 }, []);
82 }
83 `,
84 },
85 {
86 code: normalizeIndent`
87 function MyComponent() {
88 const local1 = {};
89 {
90 const local2 = {};
91 useEffect(() => {
92 console.log(local1);
93 console.log(local2);
94 });
95 }
96 }
97 `,
98 },
99 {
100 code: normalizeIndent`
101 function MyComponent() {
102 const local1 = someFunc();
103 {
104 const local2 = someFunc();
105 useCallback(() => {
106 console.log(local1);
107 console.log(local2);
108 }, [local1, local2]);
109 }
110 }
111 `,
112 },
113 {
114 code: normalizeIndent`
115 function MyComponent() {
116 const local1 = someFunc();
117 function MyNestedComponent() {
118 const local2 = someFunc();
119 useCallback(() => {
120 console.log(local1);
121 console.log(local2);
122 }, [local2]);
123 }
124 }
125 `,
126 },
127 {
128 code: normalizeIndent`
129 function MyComponent() {
130 const local = someFunc();
131 useEffect(() => {
132 console.log(local);
133 console.log(local);
134 }, [local]);
135 }
136 `,
137 },
138 {
139 code: normalizeIndent`
140 function MyComponent() {
141 useEffect(() => {
142 console.log(unresolved);
143 }, []);
144 }
145 `,
146 },
147 {
148 code: normalizeIndent`
149 function MyComponent() {
150 const local = someFunc();
151 useEffect(() => {
152 console.log(local);
153 }, [,,,local,,,]);
154 }
155 `,
156 },
157 {
158 // Regression test
159 code: normalizeIndent`
160 function MyComponent({ foo }) {
161 useEffect(() => {
162 console.log(foo.length);
163 }, [foo]);
164 }
165 `,
166 },
167 {
168 // Regression test
169 code: normalizeIndent`
170 function MyComponent({ foo }) {
171 useEffect(() => {
172 console.log(foo.length);
173 console.log(foo.slice(0));
174 }, [foo]);
175 }
176 `,
177 },
178 {
179 // Regression test
180 code: normalizeIndent`
181 function MyComponent({ history }) {
182 useEffect(() => {
183 return history.listen();
184 }, [history]);
185 }
186 `,
187 },
188 {
189 // Valid because they have meaning without deps.
190 code: normalizeIndent`
191 function MyComponent(props) {
192 useEffect(() => {});
193 useLayoutEffect(() => {});
194 useImperativeHandle(props.innerRef, () => {});
195 }
196 `,
197 },
198 {
199 code: normalizeIndent`
200 function MyComponent(props) {
201 useEffect(() => {
202 console.log(props.foo);
203 }, [props.foo]);
204 }
205 `,
206 },
207 {
208 code: normalizeIndent`
209 function MyComponent(props) {
210 useEffect(() => {
211 console.log(props.foo);
212 console.log(props.bar);
213 }, [props.bar, props.foo]);
214 }
215 `,
216 },
217 {
218 code: normalizeIndent`
219 function MyComponent(props) {
220 useEffect(() => {
221 console.log(props.foo);
222 console.log(props.bar);
223 }, [props.foo, props.bar]);
224 }
225 `,
226 },
227 {
228 code: normalizeIndent`
229 function MyComponent(props) {
230 const local = someFunc();
231 useEffect(() => {
232 console.log(props.foo);
233 console.log(props.bar);
234 console.log(local);
235 }, [props.foo, props.bar, local]);
236 }
237 `,
238 },
239 {
240 // [props, props.foo] is technically unnecessary ('props' covers 'props.foo').
241 // However, it's valid for effects to over-specify their deps.
242 // So we don't warn about this. We *would* warn about useMemo/useCallback.
243 code: normalizeIndent`
244 function MyComponent(props) {
245 const local = {};
246 useEffect(() => {
247 console.log(props.foo);
248 console.log(props.bar);
249 }, [props, props.foo]);
250
251 let color = someFunc();
252 useEffect(() => {
253 console.log(props.foo.bar.baz);
254 console.log(color);
255 }, [props.foo, props.foo.bar.baz, color]);
256 }
257 `,
258 },
259 // Nullish coalescing and optional chaining
260 {
261 code: normalizeIndent`
262 function MyComponent(props) {
263 useEffect(() => {
264 console.log(props.foo?.bar?.baz ?? null);
265 }, [props.foo]);
266 }
267 `,
268 },
269 {
270 code: normalizeIndent`
271 function MyComponent(props) {
272 useEffect(() => {
273 console.log(props.foo?.bar);
274 }, [props.foo?.bar]);
275 }
276 `,
277 },
278 {
279 code: normalizeIndent`
280 function MyComponent(props) {
281 useEffect(() => {
282 console.log(props.foo?.bar);
283 }, [props.foo.bar]);
284 }
285 `,
286 },
287 {
288 code: normalizeIndent`
289 function MyComponent(props) {
290 useEffect(() => {
291 console.log(props.foo.bar);
292 }, [props.foo?.bar]);
293 }
294 `,
295 },
296 {
297 code: normalizeIndent`
298 function MyComponent(props) {
299 useEffect(() => {
300 console.log(props.foo.bar);
301 console.log(props.foo?.bar);
302 }, [props.foo?.bar]);
303 }
304 `,
305 },
306 {
307 code: normalizeIndent`
308 function MyComponent(props) {
309 useEffect(() => {
310 console.log(props.foo.bar);
311 console.log(props.foo?.bar);
312 }, [props.foo.bar]);
313 }
314 `,
315 },
316 {
317 code: normalizeIndent`
318 function MyComponent(props) {
319 useEffect(() => {
320 console.log(props.foo);
321 console.log(props.foo?.bar);
322 }, [props.foo]);
323 }
324 `,
325 },
326 {
327 code: normalizeIndent`
328 function MyComponent(props) {
329 useEffect(() => {
330 console.log(props.foo?.toString());
331 }, [props.foo]);
332 }
333 `,
334 },
335 {
336 code: normalizeIndent`
337 function MyComponent(props) {
338 useMemo(() => {
339 console.log(props.foo?.toString());
340 }, [props.foo]);
341 }
342 `,
343 },
344 {
345 code: normalizeIndent`
346 function MyComponent(props) {
347 useCallback(() => {
348 console.log(props.foo?.toString());
349 }, [props.foo]);
350 }
351 `,
352 },
353 {
354 code: normalizeIndent`
355 function MyComponent(props) {
356 useCallback(() => {
357 console.log(props.foo.bar?.toString());
358 }, [props.foo.bar]);
359 }
360 `,
361 },
362 {
363 code: normalizeIndent`
364 function MyComponent(props) {
365 useCallback(() => {
366 console.log(props.foo?.bar?.toString());
367 }, [props.foo.bar]);
368 }
369 `,
370 },
371 {
372 code: normalizeIndent`
373 function MyComponent(props) {
374 useCallback(() => {
375 console.log(props.foo.bar.toString());
376 }, [props?.foo?.bar]);
377 }
378 `,
379 },
380 {
381 code: normalizeIndent`
382 function MyComponent(props) {
383 useCallback(() => {
384 console.log(props.foo?.bar?.baz);
385 }, [props?.foo.bar?.baz]);
386 }
387 `,
388 },
389 {
390 code: normalizeIndent`
391 function MyComponent() {
392 const myEffect = () => {
393 // Doesn't use anything
394 };
395 useEffect(myEffect, []);
396 }
397 `,
398 },
399 {
400 code: normalizeIndent`
401 const local = {};
402 function MyComponent() {
403 const myEffect = () => {
404 console.log(local);
405 };
406 useEffect(myEffect, []);
407 }
408 `,
409 },
410 {
411 code: normalizeIndent`
412 const local = {};
413 function MyComponent() {
414 function myEffect() {
415 console.log(local);
416 }
417 useEffect(myEffect, []);
418 }
419 `,
420 },
421 {
422 code: normalizeIndent`
423 function MyComponent() {
424 const local = someFunc();
425 function myEffect() {
426 console.log(local);
427 }
428 useEffect(myEffect, [local]);
429 }
430 `,
431 },
432 {
433 code: normalizeIndent`
434 function MyComponent() {
435 function myEffect() {
436 console.log(global);
437 }
438 useEffect(myEffect, []);
439 }
440 `,
441 },
442 {
443 code: normalizeIndent`
444 const local = {};
445 function MyComponent() {
446 const myEffect = () => {
447 otherThing()
448 }
449 const otherThing = () => {
450 console.log(local);
451 }
452 useEffect(myEffect, []);
453 }
454 `,
455 },
456 {
457 // Valid because even though we don't inspect the function itself,
458 // at least it's passed as a dependency.
459 code: normalizeIndent`
460 function MyComponent({delay}) {
461 const local = {};
462 const myEffect = debounce(() => {
463 console.log(local);
464 }, delay);
465 useEffect(myEffect, [myEffect]);
466 }
467 `,
468 },
469 {
470 code: normalizeIndent`
471 function MyComponent({myEffect}) {
472 useEffect(myEffect, [,myEffect]);
473 }
474 `,
475 },
476 {
477 code: normalizeIndent`
478 function MyComponent({myEffect}) {
479 useEffect(myEffect, [,myEffect,,]);
480 }
481 `,
482 },
483 {
484 code: normalizeIndent`
485 let local = {};
486 function myEffect() {
487 console.log(local);
488 }
489 function MyComponent() {
490 useEffect(myEffect, []);
491 }
492 `,
493 },
494 {
495 code: normalizeIndent`
496 function MyComponent({myEffect}) {
497 useEffect(myEffect, [myEffect]);
498 }
499 `,
500 },
501 {
502 // Valid because has no deps.
503 code: normalizeIndent`
504 function MyComponent({myEffect}) {
505 useEffect(myEffect);
506 }
507 `,
508 },
509 {
510 code: normalizeIndent`
511 function MyComponent(props) {
512 useCustomEffect(() => {
513 console.log(props.foo);
514 });
515 }
516 `,
517 options: [{additionalHooks: 'useCustomEffect'}],
518 },
519 {
520 // behaves like no deps
521 code: normalizeIndent`
522 function MyComponent(props) {
523 useSpecialEffect(() => {
524 console.log(props.foo);
525 }, null);
526 }
527 `,
528 options: [
529 {
530 additionalHooks: 'useSpecialEffect',
531 experimental_autoDependenciesHooks: ['useSpecialEffect'],
532 },
533 ],
534 },
535 {
536 code: normalizeIndent`
537 function MyComponent(props) {
538 useCustomEffect(() => {
539 console.log(props.foo);
540 }, [props.foo]);
541 }
542 `,
543 options: [{additionalHooks: 'useCustomEffect'}],
544 },
545 {
546 code: normalizeIndent`
547 function MyComponent(props) {
548 useCustomEffect(() => {
549 console.log(props.foo);
550 }, []);
551 }
552 `,
553 options: [{additionalHooks: 'useAnotherEffect'}],
554 },
555 {
556 code: normalizeIndent`
557 function MyComponent(props) {
558 useWithoutEffectSuffix(() => {
559 console.log(props.foo);
560 }, []);
561 }
562 `,
563 },
564 {
565 code: normalizeIndent`
566 function MyComponent(props) {
567 return renderHelperConfusedWithEffect(() => {
568 console.log(props.foo);
569 }, []);
570 }
571 `,
572 },
573 {
574 // Valid because we don't care about hooks outside of components.
575 code: normalizeIndent`
576 const local = {};
577 useEffect(() => {
578 console.log(local);
579 }, []);
580 `,
581 },
582 {
583 // Valid because we don't care about hooks outside of components.
584 code: normalizeIndent`
585 const local1 = {};
586 {
587 const local2 = {};
588 useEffect(() => {
589 console.log(local1);
590 console.log(local2);
591 }, []);
592 }
593 `,
594 },
595 {
596 code: normalizeIndent`
597 function MyComponent() {
598 const ref = useRef();
599 useEffect(() => {
600 console.log(ref.current);
601 }, [ref]);
602 }
603 `,
604 },
605 {
606 code: normalizeIndent`
607 function MyComponent() {
608 const ref = useRef();
609 useEffect(() => {
610 console.log(ref.current);
611 }, []);
612 }
613 `,
614 },
615 {
616 code: normalizeIndent`
617 function MyComponent({ maybeRef2, foo }) {
618 const definitelyRef1 = useRef();
619 const definitelyRef2 = useRef();
620 const maybeRef1 = useSomeOtherRefyThing();
621 const [state1, setState1] = useState();
622 const [state2, setState2] = React.useState();
623 const [state3, dispatch1] = useReducer();
624 const [state4, dispatch2] = React.useReducer();
625 const [state5, maybeSetState] = useFunnyState();
626 const [state6, maybeDispatch] = useFunnyReducer();
627 const [state9, dispatch5] = useActionState();
628 const [state10, dispatch6] = React.useActionState();
629 const [isPending1] = useTransition();
630 const [isPending2, startTransition2] = useTransition();
631 const [isPending3] = React.useTransition();
632 const [isPending4, startTransition4] = React.useTransition();
633 const mySetState = useCallback(() => {}, []);
634 let myDispatch = useCallback(() => {}, []);
635
636 useEffect(() => {
637 // Known to be static
638 console.log(definitelyRef1.current);
639 console.log(definitelyRef2.current);
640 console.log(maybeRef1.current);
641 console.log(maybeRef2.current);
642 setState1();
643 setState2();
644 dispatch1();
645 dispatch2();
646 dispatch5();
647 dispatch6();
648 startTransition1();
649 startTransition2();
650 startTransition3();
651 startTransition4();
652
653 // Dynamic
654 console.log(state1);
655 console.log(state2);
656 console.log(state3);
657 console.log(state4);
658 console.log(state5);
659 console.log(state6);
660 console.log(isPending2);
661 console.log(isPending4);
662 mySetState();
663 myDispatch();
664
665 // Not sure; assume dynamic
666 maybeSetState();
667 maybeDispatch();
668 }, [
669 // Dynamic
670 state1, state2, state3, state4, state5, state6, state9, state10,
671 maybeRef1, maybeRef2,
672 isPending2, isPending4,
673
674 // Not sure; assume dynamic
675 mySetState, myDispatch,
676 maybeSetState, maybeDispatch
677
678 // In this test, we don't specify static deps.
679 // That should be okay.
680 ]);
681 }
682 `,
683 },
684 {
685 code: normalizeIndent`
686 function MyComponent({ maybeRef2 }) {
687 const definitelyRef1 = useRef();
688 const definitelyRef2 = useRef();
689 const maybeRef1 = useSomeOtherRefyThing();
690
691 const [state1, setState1] = useState();
692 const [state2, setState2] = React.useState();
693 const [state3, dispatch1] = useReducer();
694 const [state4, dispatch2] = React.useReducer();
695
696 const [state5, maybeSetState] = useFunnyState();
697 const [state6, maybeDispatch] = useFunnyReducer();
698
699 const mySetState = useCallback(() => {}, []);
700 let myDispatch = useCallback(() => {}, []);
701
702 useEffect(() => {
703 // Known to be static
704 console.log(definitelyRef1.current);
705 console.log(definitelyRef2.current);
706 console.log(maybeRef1.current);
707 console.log(maybeRef2.current);
708 setState1();
709 setState2();
710 dispatch1();
711 dispatch2();
712
713 // Dynamic
714 console.log(state1);
715 console.log(state2);
716 console.log(state3);
717 console.log(state4);
718 console.log(state5);
719 console.log(state6);
720 mySetState();
721 myDispatch();
722
723 // Not sure; assume dynamic
724 maybeSetState();
725 maybeDispatch();
726 }, [
727 // Dynamic
728 state1, state2, state3, state4, state5, state6,
729 maybeRef1, maybeRef2,
730
731 // Not sure; assume dynamic
732 mySetState, myDispatch,
733 maybeSetState, maybeDispatch,
734
735 // In this test, we specify static deps.
736 // That should be okay too!
737 definitelyRef1, definitelyRef2, setState1, setState2, dispatch1, dispatch2
738 ]);
739 }
740 `,
741 },
742 {
743 code: normalizeIndent`
744 const MyComponent = forwardRef((props, ref) => {
745 useImperativeHandle(ref, () => ({
746 focus() {
747 alert(props.hello);
748 }
749 }))
750 });
751 `,
752 },
753 {
754 code: normalizeIndent`
755 const MyComponent = forwardRef((props, ref) => {
756 useImperativeHandle(ref, () => ({
757 focus() {
758 alert(props.hello);
759 }
760 }), [props.hello])
761 });
762 `,
763 },
764 {
765 // This is not ideal but warning would likely create
766 // too many false positives. We do, however, prevent
767 // direct assignments.
768 code: normalizeIndent`
769 function MyComponent(props) {
770 let obj = someFunc();
771 useEffect(() => {
772 obj.foo = true;
773 }, [obj]);
774 }
775 `,
776 },
777 {
778 code: normalizeIndent`
779 function MyComponent(props) {
780 let foo = {}
781 useEffect(() => {
782 foo.bar.baz = 43;
783 }, [foo.bar]);
784 }
785 `,
786 },
787 {
788 // Valid because we assign ref.current
789 // ourselves. Therefore it's likely not
790 // a ref managed by React.
791 code: normalizeIndent`
792 function MyComponent() {
793 const myRef = useRef();
794 useEffect(() => {
795 const handleMove = () => {};
796 myRef.current = {};
797 return () => {
798 console.log(myRef.current.toString())
799 };
800 }, []);
801 return <div />;
802 }
803 `,
804 },
805 {
806 // Valid because we assign ref.current
807 // ourselves. Therefore it's likely not
808 // a ref managed by React.
809 code: normalizeIndent`
810 function MyComponent() {
811 const myRef = useRef();
812 useEffect(() => {
813 const handleMove = () => {};
814 myRef.current = {};
815 return () => {
816 console.log(myRef?.current?.toString())
817 };
818 }, []);
819 return <div />;
820 }
821 `,
822 },
823 {
824 // Valid because we assign ref.current
825 // ourselves. Therefore it's likely not
826 // a ref managed by React.
827 code: normalizeIndent`
828 function useMyThing(myRef) {
829 useEffect(() => {
830 const handleMove = () => {};
831 myRef.current = {};
832 return () => {
833 console.log(myRef.current.toString())
834 };
835 }, [myRef]);
836 }
837 `,
838 },
839 {
840 // Valid because the ref is captured.
841 code: normalizeIndent`
842 function MyComponent() {
843 const myRef = useRef();
844 useEffect(() => {
845 const handleMove = () => {};
846 const node = myRef.current;
847 node.addEventListener('mousemove', handleMove);
848 return () => node.removeEventListener('mousemove', handleMove);
849 }, []);
850 return <div ref={myRef} />;
851 }
852 `,
853 },
854 {
855 // Valid because the ref is captured.
856 code: normalizeIndent`
857 function useMyThing(myRef) {
858 useEffect(() => {
859 const handleMove = () => {};
860 const node = myRef.current;
861 node.addEventListener('mousemove', handleMove);
862 return () => node.removeEventListener('mousemove', handleMove);
863 }, [myRef]);
864 return <div ref={myRef} />;
865 }
866 `,
867 },
868 {
869 // Valid because it's not an effect.
870 code: normalizeIndent`
871 function useMyThing(myRef) {
872 useCallback(() => {
873 const handleMouse = () => {};
874 myRef.current.addEventListener('mousemove', handleMouse);
875 myRef.current.addEventListener('mousein', handleMouse);
876 return function() {
877 setTimeout(() => {
878 myRef.current.removeEventListener('mousemove', handleMouse);
879 myRef.current.removeEventListener('mousein', handleMouse);
880 });
881 }
882 }, [myRef]);
883 }
884 `,
885 },
886 {
887 // Valid because we read ref.current in a function that isn't cleanup.
888 code: normalizeIndent`
889 function useMyThing() {
890 const myRef = useRef();
891 useEffect(() => {
892 const handleMove = () => {
893 console.log(myRef.current)
894 };
895 window.addEventListener('mousemove', handleMove);
896 return () => window.removeEventListener('mousemove', handleMove);
897 }, []);
898 return <div ref={myRef} />;
899 }
900 `,
901 },
902 {
903 // Valid because we read ref.current in a function that isn't cleanup.
904 code: normalizeIndent`
905 function useMyThing() {
906 const myRef = useRef();
907 useEffect(() => {
908 const handleMove = () => {
909 return () => window.removeEventListener('mousemove', handleMove);
910 };
911 window.addEventListener('mousemove', handleMove);
912 return () => {};
913 }, []);
914 return <div ref={myRef} />;
915 }
916 `,
917 },
918 {
919 // Valid because it's a primitive constant.
920 code: normalizeIndent`
921 function MyComponent() {
922 const local1 = 42;
923 const local2 = '42';
924 const local3 = null;
925 useEffect(() => {
926 console.log(local1);
927 console.log(local2);
928 console.log(local3);
929 }, []);
930 }
931 `,
932 },
933 {
934 // It's not a mistake to specify constant values though.
935 code: normalizeIndent`
936 function MyComponent() {
937 const local1 = 42;
938 const local2 = '42';
939 const local3 = null;
940 useEffect(() => {
941 console.log(local1);
942 console.log(local2);
943 console.log(local3);
944 }, [local1, local2, local3]);
945 }
946 `,
947 },
948 {
949 // It is valid for effects to over-specify their deps.
950 code: normalizeIndent`
951 function MyComponent(props) {
952 const local = props.local;
953 useEffect(() => {}, [local]);
954 }
955 `,
956 },
957 {
958 // Valid even though activeTab is "unused".
959 // We allow over-specifying deps for effects, but not callbacks or memo.
960 code: normalizeIndent`
961 function Foo({ activeTab }) {
962 useEffect(() => {
963 window.scrollTo(0, 0);
964 }, [activeTab]);
965 }
966 `,
967 },
968 {
969 // It is valid to specify broader effect deps than strictly necessary.
970 // Don't warn for this.
971 code: normalizeIndent`
972 function MyComponent(props) {
973 useEffect(() => {
974 console.log(props.foo.bar.baz);
975 }, [props]);
976 useEffect(() => {
977 console.log(props.foo.bar.baz);
978 }, [props.foo]);
979 useEffect(() => {
980 console.log(props.foo.bar.baz);
981 }, [props.foo.bar]);
982 useEffect(() => {
983 console.log(props.foo.bar.baz);
984 }, [props.foo.bar.baz]);
985 }
986 `,
987 },
988 {
989 // It is *also* valid to specify broader memo/callback deps than strictly necessary.
990 // Don't warn for this either.
991 code: normalizeIndent`
992 function MyComponent(props) {
993 const fn = useCallback(() => {
994 console.log(props.foo.bar.baz);
995 }, [props]);
996 const fn2 = useCallback(() => {
997 console.log(props.foo.bar.baz);
998 }, [props.foo]);
999 const fn3 = useMemo(() => {
1000 console.log(props.foo.bar.baz);
1001 }, [props.foo.bar]);
1002 const fn4 = useMemo(() => {
1003 console.log(props.foo.bar.baz);
1004 }, [props.foo.bar.baz]);
1005 }
1006 `,
1007 },
1008 {
1009 // Declaring handleNext is optional because
1010 // it doesn't use anything in the function scope.
1011 code: normalizeIndent`
1012 function MyComponent(props) {
1013 function handleNext1() {
1014 console.log('hello');
1015 }
1016 const handleNext2 = () => {
1017 console.log('hello');
1018 };
1019 let handleNext3 = function() {
1020 console.log('hello');
1021 };
1022 useEffect(() => {
1023 return Store.subscribe(handleNext1);
1024 }, []);
1025 useLayoutEffect(() => {
1026 return Store.subscribe(handleNext2);
1027 }, []);
1028 useMemo(() => {
1029 return Store.subscribe(handleNext3);
1030 }, []);
1031 }
1032 `,
1033 },
1034 {
1035 // Declaring handleNext is optional because
1036 // it doesn't use anything in the function scope.
1037 code: normalizeIndent`
1038 function MyComponent(props) {
1039 function handleNext() {
1040 console.log('hello');
1041 }
1042 useEffect(() => {
1043 return Store.subscribe(handleNext);
1044 }, []);
1045 useLayoutEffect(() => {
1046 return Store.subscribe(handleNext);
1047 }, []);
1048 useMemo(() => {
1049 return Store.subscribe(handleNext);
1050 }, []);
1051 }
1052 `,
1053 },
1054 {
1055 // Declaring handleNext is optional because
1056 // everything they use is fully static.
1057 code: normalizeIndent`
1058 function MyComponent(props) {
1059 let [, setState] = useState();
1060 let [, dispatch] = React.useReducer();
1061
1062 function handleNext1(value) {
1063 let value2 = value * 100;
1064 setState(value2);
1065 console.log('hello');
1066 }
1067 const handleNext2 = (value) => {
1068 setState(foo(value));
1069 console.log('hello');
1070 };
1071 let handleNext3 = function(value) {
1072 console.log(value);
1073 dispatch({ type: 'x', value });
1074 };
1075 useEffect(() => {
1076 return Store.subscribe(handleNext1);
1077 }, []);
1078 useLayoutEffect(() => {
1079 return Store.subscribe(handleNext2);
1080 }, []);
1081 useMemo(() => {
1082 return Store.subscribe(handleNext3);
1083 }, []);
1084 }
1085 `,
1086 },
1087 {
1088 code: normalizeIndent`
1089 function useInterval(callback, delay) {
1090 const savedCallback = useRef();
1091 useEffect(() => {
1092 savedCallback.current = callback;
1093 });
1094 useEffect(() => {
1095 function tick() {
1096 savedCallback.current();
1097 }
1098 if (delay !== null) {
1099 let id = setInterval(tick, delay);
1100 return () => clearInterval(id);
1101 }
1102 }, [delay]);
1103 }
1104 `,
1105 },
1106 {
1107 code: normalizeIndent`
1108 function Counter() {
1109 const [count, setCount] = useState(0);
1110
1111 useEffect(() => {
1112 let id = setInterval(() => {
1113 setCount(c => c + 1);
1114 }, 1000);
1115 return () => clearInterval(id);
1116 }, []);
1117
1118 return <h1>{count}</h1>;
1119 }
1120 `,
1121 },
1122 {
1123 code: normalizeIndent`
1124 function Counter(unstableProp) {
1125 let [count, setCount] = useState(0);
1126 setCount = unstableProp
1127 useEffect(() => {
1128 let id = setInterval(() => {
1129 setCount(c => c + 1);
1130 }, 1000);
1131 return () => clearInterval(id);
1132 }, [setCount]);
1133
1134 return <h1>{count}</h1>;
1135 }
1136 `,
1137 },
1138 {
1139 code: normalizeIndent`
1140 function Counter() {
1141 const [count, setCount] = useState(0);
1142
1143 function tick() {
1144 setCount(c => c + 1);
1145 }
1146
1147 useEffect(() => {
1148 let id = setInterval(() => {
1149 tick();
1150 }, 1000);
1151 return () => clearInterval(id);
1152 }, []);
1153
1154 return <h1>{count}</h1>;
1155 }
1156 `,
1157 },
1158 {
1159 code: normalizeIndent`
1160 function Counter() {
1161 const [count, dispatch] = useReducer((state, action) => {
1162 if (action === 'inc') {
1163 return state + 1;
1164 }
1165 }, 0);
1166
1167 useEffect(() => {
1168 let id = setInterval(() => {
1169 dispatch('inc');
1170 }, 1000);
1171 return () => clearInterval(id);
1172 }, []);
1173
1174 return <h1>{count}</h1>;
1175 }
1176 `,
1177 },
1178 {
1179 code: normalizeIndent`
1180 function Counter() {
1181 const [count, dispatch] = useReducer((state, action) => {
1182 if (action === 'inc') {
1183 return state + 1;
1184 }
1185 }, 0);
1186
1187 const tick = () => {
1188 dispatch('inc');
1189 };
1190
1191 useEffect(() => {
1192 let id = setInterval(tick, 1000);
1193 return () => clearInterval(id);
1194 }, []);
1195
1196 return <h1>{count}</h1>;
1197 }
1198 `,
1199 },
1200 {
1201 // Regression test for a crash
1202 code: normalizeIndent`
1203 function Podcasts() {
1204 useEffect(() => {
1205 setPodcasts([]);
1206 }, []);
1207 let [podcasts, setPodcasts] = useState(null);
1208 }
1209 `,
1210 },
1211 {
1212 code: normalizeIndent`
1213 function withFetch(fetchPodcasts) {
1214 return function Podcasts({ id }) {
1215 let [podcasts, setPodcasts] = useState(null);
1216 useEffect(() => {
1217 fetchPodcasts(id).then(setPodcasts);
1218 }, [id]);
1219 }
1220 }
1221 `,
1222 },
1223 {
1224 code: normalizeIndent`
1225 function Podcasts({ id }) {
1226 let [podcasts, setPodcasts] = useState(null);
1227 useEffect(() => {
1228 function doFetch({ fetchPodcasts }) {
1229 fetchPodcasts(id).then(setPodcasts);
1230 }
1231 doFetch({ fetchPodcasts: API.fetchPodcasts });
1232 }, [id]);
1233 }
1234 `,
1235 },
1236 {
1237 code: normalizeIndent`
1238 function Counter() {
1239 let [count, setCount] = useState(0);
1240
1241 function increment(x) {
1242 return x + 1;
1243 }
1244
1245 useEffect(() => {
1246 let id = setInterval(() => {
1247 setCount(increment);
1248 }, 1000);
1249 return () => clearInterval(id);
1250 }, []);
1251
1252 return <h1>{count}</h1>;
1253 }
1254 `,
1255 },
1256 {
1257 code: normalizeIndent`
1258 function Counter() {
1259 let [count, setCount] = useState(0);
1260
1261 function increment(x) {
1262 return x + 1;
1263 }
1264
1265 useEffect(() => {
1266 let id = setInterval(() => {
1267 setCount(count => increment(count));
1268 }, 1000);
1269 return () => clearInterval(id);
1270 }, []);
1271
1272 return <h1>{count}</h1>;
1273 }
1274 `,
1275 },
1276 {
1277 code: normalizeIndent`
1278 import increment from './increment';
1279 function Counter() {
1280 let [count, setCount] = useState(0);
1281
1282 useEffect(() => {
1283 let id = setInterval(() => {
1284 setCount(count => count + increment);
1285 }, 1000);
1286 return () => clearInterval(id);
1287 }, []);
1288
1289 return <h1>{count}</h1>;
1290 }
1291 `,
1292 },
1293 {
1294 code: normalizeIndent`
1295 function withStuff(increment) {
1296 return function Counter() {
1297 let [count, setCount] = useState(0);
1298
1299 useEffect(() => {
1300 let id = setInterval(() => {
1301 setCount(count => count + increment);
1302 }, 1000);
1303 return () => clearInterval(id);
1304 }, []);
1305
1306 return <h1>{count}</h1>;
1307 }
1308 }
1309 `,
1310 },
1311 {
1312 code: normalizeIndent`
1313 function App() {
1314 const [query, setQuery] = useState('react');
1315 const [state, setState] = useState(null);
1316 useEffect(() => {
1317 let ignore = false;
1318 fetchSomething();
1319 async function fetchSomething() {
1320 const result = await (await fetch('http://hn.algolia.com/api/v1/search?query=' + query)).json();
1321 if (!ignore) setState(result);
1322 }
1323 return () => { ignore = true; };
1324 }, [query]);
1325 return (
1326 <>
1327 <input value={query} onChange={e => setQuery(e.target.value)} />
1328 {JSON.stringify(state)}
1329 </>
1330 );
1331 }
1332 `,
1333 },
1334 {
1335 code: normalizeIndent`
1336 function Example() {
1337 const foo = useCallback(() => {
1338 foo();
1339 }, []);
1340 }
1341 `,
1342 },
1343 {
1344 code: normalizeIndent`
1345 function Example({ prop }) {
1346 const foo = useCallback(() => {
1347 if (prop) {
1348 foo();
1349 }
1350 }, [prop]);
1351 }
1352 `,
1353 },
1354 {
1355 code: normalizeIndent`
1356 function Hello() {
1357 const [state, setState] = useState(0);
1358 useEffect(() => {
1359 const handleResize = () => setState(window.innerWidth);
1360 window.addEventListener('resize', handleResize);
1361 return () => window.removeEventListener('resize', handleResize);
1362 });
1363 }
1364 `,
1365 },
1366 // Ignore arguments keyword for arrow functions.
1367 {
1368 code: normalizeIndent`
1369 function Example() {
1370 useEffect(() => {
1371 arguments
1372 }, [])
1373 }
1374 `,
1375 },
1376 {
1377 code: normalizeIndent`
1378 function Example() {
1379 useEffect(() => {
1380 const bar = () => {
1381 arguments;
1382 };
1383 bar();
1384 }, [])
1385 }
1386 `,
1387 },
1388 // Regression test.
1389 {
1390 code: normalizeIndent`
1391 function Example(props) {
1392 useEffect(() => {
1393 let topHeight = 0;
1394 topHeight = props.upperViewHeight;
1395 }, [props.upperViewHeight]);
1396 }
1397 `,
1398 },
1399 // Regression test.
1400 {
1401 code: normalizeIndent`
1402 function Example(props) {
1403 useEffect(() => {
1404 let topHeight = 0;
1405 topHeight = props?.upperViewHeight;
1406 }, [props?.upperViewHeight]);
1407 }
1408 `,
1409 },
1410 // Regression test.
1411 {
1412 code: normalizeIndent`
1413 function Example(props) {
1414 useEffect(() => {
1415 let topHeight = 0;
1416 topHeight = props?.upperViewHeight;
1417 }, [props]);
1418 }
1419 `,
1420 },
1421 {
1422 code: normalizeIndent`
1423 function useFoo(foo){
1424 return useMemo(() => foo, [foo]);
1425 }
1426 `,
1427 },
1428 {
1429 code: normalizeIndent`
1430 function useFoo(){
1431 const foo = "hi!";
1432 return useMemo(() => foo, [foo]);
1433 }
1434 `,
1435 },
1436 {
1437 code: normalizeIndent`
1438 function useFoo(){
1439 let {foo} = {foo: 1};
1440 return useMemo(() => foo, [foo]);
1441 }
1442 `,
1443 },
1444 {
1445 code: normalizeIndent`
1446 function useFoo(){
1447 let [foo] = [1];
1448 return useMemo(() => foo, [foo]);
1449 }
1450 `,
1451 },
1452 {
1453 code: normalizeIndent`
1454 function useFoo() {
1455 const foo = "fine";
1456 if (true) {
1457 // Shadowed variable with constant construction in a nested scope is fine.
1458 const foo = {};
1459 }
1460 return useMemo(() => foo, [foo]);
1461 }
1462 `,
1463 },
1464 {
1465 code: normalizeIndent`
1466 function MyComponent({foo}) {
1467 return useMemo(() => foo, [foo])
1468 }
1469 `,
1470 },
1471 {
1472 code: normalizeIndent`
1473 function MyComponent() {
1474 const foo = true ? "fine" : "also fine";
1475 return useMemo(() => foo, [foo]);
1476 }
1477 `,
1478 },
1479 {
1480 code: normalizeIndent`
1481 function MyComponent() {
1482 useEffect(() => {
1483 console.log('banana banana banana');
1484 }, undefined);
1485 }
1486 `,
1487 },
1488 {
1489 // Test settings-based additionalHooks - should work with settings
1490 code: normalizeIndent`
1491 function MyComponent(props) {
1492 useCustomEffect(() => {
1493 console.log(props.foo);
1494 });
1495 }
1496 `,
1497 settings: {
1498 'react-hooks': {
1499 additionalEffectHooks: 'useCustomEffect',
1500 },
1501 },
1502 },
1503 {
1504 // Test settings-based additionalHooks - should work with dependencies
1505 code: normalizeIndent`
1506 function MyComponent(props) {
1507 useCustomEffect(() => {
1508 console.log(props.foo);
1509 }, [props.foo]);
1510 }
1511 `,
1512 settings: {
1513 'react-hooks': {
1514 additionalEffectHooks: 'useCustomEffect',
1515 },
1516 },
1517 },
1518 {
1519 // Test that rule-level additionalHooks takes precedence over settings
1520 code: normalizeIndent`
1521 function MyComponent(props) {
1522 useCustomEffect(() => {
1523 console.log(props.foo);
1524 }, []);
1525 }
1526 `,
1527 options: [{additionalHooks: 'useAnotherEffect'}],
1528 settings: {
1529 'react-hooks': {
1530 additionalEffectHooks: 'useCustomEffect',
1531 },
1532 },
1533 },
1534 {
1535 // Test settings with multiple hooks pattern
1536 code: normalizeIndent`
1537 function MyComponent(props) {
1538 useCustomEffect(() => {
1539 console.log(props.foo);
1540 }, [props.foo]);
1541 useAnotherEffect(() => {
1542 console.log(props.bar);
1543 }, [props.bar]);
1544 }
1545 `,
1546 settings: {
1547 'react-hooks': {
1548 additionalEffectHooks: '(useCustomEffect|useAnotherEffect)',
1549 },
1550 },
1551 },
1552 {
1553 code: normalizeIndent`
1554 function MyComponent({ theme }) {
1555 const onStuff = useEffectEvent(() => {
1556 showNotification(theme);
1557 });
1558 useEffect(() => {
1559 onStuff();
1560 }, []);
1561 React.useEffect(() => {
1562 onStuff();
1563 }, []);
1564 }
1565 `,
1566 },
1567 ],
1568 invalid: [
1569 {
1570 code: normalizeIndent`
1571 function MyComponent(props) {
1572 useSpecialEffect(() => {
1573 console.log(props.foo);
1574 }, null);
1575 }
1576 `,
1577 options: [{additionalHooks: 'useSpecialEffect'}],
1578 errors: [
1579 {
1580 message:
1581 "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.",
1582 },
1583 {
1584 message:
1585 "React Hook useSpecialEffect has a missing dependency: 'props.foo'. Either include it or remove the dependency array.",
1586 suggestions: [
1587 {
1588 desc: 'Update the dependencies array to be: [props.foo]',
1589 output: normalizeIndent`
1590 function MyComponent(props) {
1591 useSpecialEffect(() => {
1592 console.log(props.foo);
1593 }, [props.foo]);
1594 }
1595 `,
1596 },
1597 ],
1598 },
1599 ],
1600 },
1601 {
1602 code: normalizeIndent`
1603 function MyComponent(props) {
1604 useCallback(() => {
1605 console.log(props.foo?.toString());
1606 }, []);
1607 }
1608 `,
1609 errors: [
1610 {
1611 message:
1612 "React Hook useCallback has a missing dependency: 'props.foo'. " +
1613 'Either include it or remove the dependency array.',
1614 suggestions: [
1615 {
1616 desc: 'Update the dependencies array to be: [props.foo]',
1617 output: normalizeIndent`
1618 function MyComponent(props) {
1619 useCallback(() => {
1620 console.log(props.foo?.toString());
1621 }, [props.foo]);
1622 }
1623 `,
1624 },
1625 ],
1626 },
1627 ],
1628 },
1629 {
1630 // Affected code should use React.useActionState instead
1631 code: normalizeIndent`
1632 function ComponentUsingFormState(props) {
1633 const [state7, dispatch3] = useFormState();
1634 const [state8, dispatch4] = ReactDOM.useFormState();
1635 useEffect(() => {
1636 dispatch3();
1637 dispatch4();
1638
1639 // dynamic
1640 console.log(state7);
1641 console.log(state8);
1642
1643 }, [state7, state8]);
1644 }
1645 `,
1646 errors: [
1647 {
1648 message:
1649 "React Hook useEffect has missing dependencies: 'dispatch3' and 'dispatch4'. " +
1650 'Either include them or remove the dependency array.',
1651 suggestions: [
1652 {
1653 desc: 'Update the dependencies array to be: [dispatch3, dispatch4, state7, state8]',
1654 output: normalizeIndent`
1655 function ComponentUsingFormState(props) {
1656 const [state7, dispatch3] = useFormState();
1657 const [state8, dispatch4] = ReactDOM.useFormState();
1658 useEffect(() => {
1659 dispatch3();
1660 dispatch4();
1661
1662 // dynamic
1663 console.log(state7);
1664 console.log(state8);
1665
1666 }, [dispatch3, dispatch4, state7, state8]);
1667 }
1668 `,
1669 },
1670 ],
1671 },
1672 ],
1673 },
1674 {
1675 code: normalizeIndent`
1676 function MyComponent(props) {
1677 useCallback(() => {
1678 console.log(props.foo?.bar.baz);
1679 }, []);
1680 }
1681 `,
1682 errors: [
1683 {
1684 message:
1685 "React Hook useCallback has a missing dependency: 'props.foo?.bar.baz'. " +
1686 'Either include it or remove the dependency array.',
1687 suggestions: [
1688 {
1689 desc: 'Update the dependencies array to be: [props.foo?.bar.baz]',
1690 output: normalizeIndent`
1691 function MyComponent(props) {
1692 useCallback(() => {
1693 console.log(props.foo?.bar.baz);
1694 }, [props.foo?.bar.baz]);
1695 }
1696 `,
1697 },
1698 ],
1699 },
1700 ],
1701 },
1702 {
1703 code: normalizeIndent`
1704 function MyComponent(props) {
1705 useCallback(() => {
1706 console.log(props.foo?.bar?.baz);
1707 }, []);
1708 }
1709 `,
1710 errors: [
1711 {
1712 message:
1713 "React Hook useCallback has a missing dependency: 'props.foo?.bar?.baz'. " +
1714 'Either include it or remove the dependency array.',
1715 suggestions: [
1716 {
1717 desc: 'Update the dependencies array to be: [props.foo?.bar?.baz]',
1718 output: normalizeIndent`
1719 function MyComponent(props) {
1720 useCallback(() => {
1721 console.log(props.foo?.bar?.baz);
1722 }, [props.foo?.bar?.baz]);
1723 }
1724 `,
1725 },
1726 ],
1727 },
1728 ],
1729 },
1730 {
1731 code: normalizeIndent`
1732 function MyComponent(props) {
1733 useCallback(() => {
1734 console.log(props.foo?.bar.toString());
1735 }, []);
1736 }
1737 `,
1738 errors: [
1739 {
1740 message:
1741 "React Hook useCallback has a missing dependency: 'props.foo?.bar'. " +
1742 'Either include it or remove the dependency array.',
1743 suggestions: [
1744 {
1745 desc: 'Update the dependencies array to be: [props.foo?.bar]',
1746 output: normalizeIndent`
1747 function MyComponent(props) {
1748 useCallback(() => {
1749 console.log(props.foo?.bar.toString());
1750 }, [props.foo?.bar]);
1751 }
1752 `,
1753 },
1754 ],
1755 },
1756 ],
1757 },
1758 {
1759 code: normalizeIndent`
1760 function MyComponent() {
1761 const local = someFunc();
1762 useEffect(() => {
1763 console.log(local);
1764 }, []);
1765 }
1766 `,
1767 errors: [
1768 {
1769 message:
1770 "React Hook useEffect has a missing dependency: 'local'. " +
1771 'Either include it or remove the dependency array.',
1772 suggestions: [
1773 {
1774 desc: 'Update the dependencies array to be: [local]',
1775 output: normalizeIndent`
1776 function MyComponent() {
1777 const local = someFunc();
1778 useEffect(() => {
1779 console.log(local);
1780 }, [local]);
1781 }
1782 `,
1783 },
1784 ],
1785 },
1786 ],
1787 },
1788 {
1789 code: normalizeIndent`
1790 function Counter(unstableProp) {
1791 let [count, setCount] = useState(0);
1792 setCount = unstableProp
1793 useEffect(() => {
1794 let id = setInterval(() => {
1795 setCount(c => c + 1);
1796 }, 1000);
1797 return () => clearInterval(id);
1798 }, []);
1799
1800 return <h1>{count}</h1>;
1801 }
1802 `,
1803 errors: [
1804 {
1805 message:
1806 "React Hook useEffect has a missing dependency: 'setCount'. " +
1807 'Either include it or remove the dependency array.',
1808 suggestions: [
1809 {
1810 desc: 'Update the dependencies array to be: [setCount]',
1811 output: normalizeIndent`
1812 function Counter(unstableProp) {
1813 let [count, setCount] = useState(0);
1814 setCount = unstableProp
1815 useEffect(() => {
1816 let id = setInterval(() => {
1817 setCount(c => c + 1);
1818 }, 1000);
1819 return () => clearInterval(id);
1820 }, [setCount]);
1821
1822 return <h1>{count}</h1>;
1823 }
1824 `,
1825 },
1826 ],
1827 },
1828 ],
1829 },
1830 {
1831 // Note: we *could* detect it's a primitive and never assigned
1832 // even though it's not a constant -- but we currently don't.
1833 // So this is an error.
1834 code: normalizeIndent`
1835 function MyComponent() {
1836 let local = 42;
1837 useEffect(() => {
1838 console.log(local);
1839 }, []);
1840 }
1841 `,
1842 errors: [
1843 {
1844 message:
1845 "React Hook useEffect has a missing dependency: 'local'. " +
1846 'Either include it or remove the dependency array.',
1847 suggestions: [
1848 {
1849 desc: 'Update the dependencies array to be: [local]',
1850 output: normalizeIndent`
1851 function MyComponent() {
1852 let local = 42;
1853 useEffect(() => {
1854 console.log(local);
1855 }, [local]);
1856 }
1857 `,
1858 },
1859 ],
1860 },
1861 ],
1862 },
1863 {
1864 // Regexes are literals but potentially stateful.
1865 code: normalizeIndent`
1866 function MyComponent() {
1867 const local = /foo/;
1868 useEffect(() => {
1869 console.log(local);
1870 }, []);
1871 }
1872 `,
1873 errors: [
1874 {
1875 message:
1876 "React Hook useEffect has a missing dependency: 'local'. " +
1877 'Either include it or remove the dependency array.',
1878 suggestions: [
1879 {
1880 desc: 'Update the dependencies array to be: [local]',
1881 output: normalizeIndent`
1882 function MyComponent() {
1883 const local = /foo/;
1884 useEffect(() => {
1885 console.log(local);
1886 }, [local]);
1887 }
1888 `,
1889 },
1890 ],
1891 },
1892 ],
1893 },
1894 {
1895 // Invalid because they don't have a meaning without deps.
1896 code: normalizeIndent`
1897 function MyComponent(props) {
1898 const value = useMemo(() => { return 2*2; });
1899 const fn = useCallback(() => { alert('foo'); });
1900 }
1901 `,
1902 // We don't know what you meant.
1903 errors: [
1904 {
1905 message:
1906 'React Hook useMemo does nothing when called with only one argument. ' +
1907 'Did you forget to pass an array of dependencies?',
1908 suggestions: undefined,
1909 },
1910 {
1911 message:
1912 'React Hook useCallback does nothing when called with only one argument. ' +
1913 'Did you forget to pass an array of dependencies?',
1914 suggestions: undefined,
1915 },
1916 ],
1917 },
1918 {
1919 // Invalid because they don't have a meaning without deps.
1920 code: normalizeIndent`
1921 function MyComponent({ fn1, fn2 }) {
1922 const value = useMemo(fn1);
1923 const fn = useCallback(fn2);
1924 }
1925 `,
1926 errors: [
1927 {
1928 message:
1929 'React Hook useMemo does nothing when called with only one argument. ' +
1930 'Did you forget to pass an array of dependencies?',
1931 suggestions: undefined,
1932 },
1933 {
1934 message:
1935 'React Hook useCallback does nothing when called with only one argument. ' +
1936 'Did you forget to pass an array of dependencies?',
1937 suggestions: undefined,
1938 },
1939 ],
1940 },
1941 {
1942 code: normalizeIndent`
1943 function MyComponent() {
1944 useEffect()
1945 useLayoutEffect()
1946 useCallback()
1947 useMemo()
1948 }
1949 `,
1950 errors: [
1951 {
1952 message:
1953 'React Hook useEffect requires an effect callback. ' +
1954 'Did you forget to pass a callback to the hook?',
1955 suggestions: undefined,
1956 },
1957 {
1958 message:
1959 'React Hook useLayoutEffect requires an effect callback. ' +
1960 'Did you forget to pass a callback to the hook?',
1961 suggestions: undefined,
1962 },
1963 {
1964 message:
1965 'React Hook useCallback requires an effect callback. ' +
1966 'Did you forget to pass a callback to the hook?',
1967 suggestions: undefined,
1968 },
1969 {
1970 message:
1971 'React Hook useMemo requires an effect callback. ' +
1972 'Did you forget to pass a callback to the hook?',
1973 suggestions: undefined,
1974 },
1975 ],
1976 },
1977 {
1978 // Regression test
1979 code: normalizeIndent`
1980 function MyComponent() {
1981 const local = someFunc();
1982 useEffect(() => {
1983 if (true) {
1984 console.log(local);
1985 }
1986 }, []);
1987 }
1988 `,
1989 errors: [
1990 {
1991 message:
1992 "React Hook useEffect has a missing dependency: 'local'. " +
1993 'Either include it or remove the dependency array.',
1994 suggestions: [
1995 {
1996 desc: 'Update the dependencies array to be: [local]',
1997 output: normalizeIndent`
1998 function MyComponent() {
1999 const local = someFunc();
2000 useEffect(() => {
2001 if (true) {
2002 console.log(local);
2003 }
2004 }, [local]);
2005 }
2006 `,
2007 },
2008 ],
2009 },
2010 ],
2011 },
2012 {
2013 // Regression test
2014 code: normalizeIndent`
2015 function MyComponent() {
2016 const local = {};
2017 useEffect(() => {
2018 try {
2019 console.log(local);
2020 } finally {}
2021 }, []);
2022 }
2023 `,
2024 errors: [
2025 {
2026 message:
2027 "React Hook useEffect has a missing dependency: 'local'. " +
2028 'Either include it or remove the dependency array.',
2029 suggestions: [
2030 {
2031 desc: 'Update the dependencies array to be: [local]',
2032 output: normalizeIndent`
2033 function MyComponent() {
2034 const local = {};
2035 useEffect(() => {
2036 try {
2037 console.log(local);
2038 } finally {}
2039 }, [local]);
2040 }
2041 `,
2042 },
2043 ],
2044 },
2045 ],
2046 },
2047 {
2048 // Regression test
2049 code: normalizeIndent`
2050 function MyComponent() {
2051 const local = {};
2052 useEffect(() => {
2053 function inner() {
2054 console.log(local);
2055 }
2056 inner();
2057 }, []);
2058 }
2059 `,
2060 errors: [
2061 {
2062 message:
2063 "React Hook useEffect has a missing dependency: 'local'. " +
2064 'Either include it or remove the dependency array.',
2065 suggestions: [
2066 {
2067 desc: 'Update the dependencies array to be: [local]',
2068 output: normalizeIndent`
2069 function MyComponent() {
2070 const local = {};
2071 useEffect(() => {
2072 function inner() {
2073 console.log(local);
2074 }
2075 inner();
2076 }, [local]);
2077 }
2078 `,
2079 },
2080 ],
2081 },
2082 ],
2083 },
2084 {
2085 code: normalizeIndent`
2086 function MyComponent() {
2087 const local1 = someFunc();
2088 {
2089 const local2 = someFunc();
2090 useEffect(() => {
2091 console.log(local1);
2092 console.log(local2);
2093 }, []);
2094 }
2095 }
2096 `,
2097 errors: [
2098 {
2099 message:
2100 "React Hook useEffect has missing dependencies: 'local1' and 'local2'. " +
2101 'Either include them or remove the dependency array.',
2102 suggestions: [
2103 {
2104 desc: 'Update the dependencies array to be: [local1, local2]',
2105 output: normalizeIndent`
2106 function MyComponent() {
2107 const local1 = someFunc();
2108 {
2109 const local2 = someFunc();
2110 useEffect(() => {
2111 console.log(local1);
2112 console.log(local2);
2113 }, [local1, local2]);
2114 }
2115 }
2116 `,
2117 },
2118 ],
2119 },
2120 ],
2121 },
2122 {
2123 code: normalizeIndent`
2124 function MyComponent() {
2125 const local1 = {};
2126 const local2 = {};
2127 useEffect(() => {
2128 console.log(local1);
2129 console.log(local2);
2130 }, [local1]);
2131 }
2132 `,
2133 errors: [
2134 {
2135 message:
2136 "React Hook useEffect has a missing dependency: 'local2'. " +
2137 'Either include it or remove the dependency array.',
2138 suggestions: [
2139 {
2140 desc: 'Update the dependencies array to be: [local1, local2]',
2141 output: normalizeIndent`
2142 function MyComponent() {
2143 const local1 = {};
2144 const local2 = {};
2145 useEffect(() => {
2146 console.log(local1);
2147 console.log(local2);
2148 }, [local1, local2]);
2149 }
2150 `,
2151 },
2152 ],
2153 },
2154 ],
2155 },
2156 {
2157 code: normalizeIndent`
2158 function MyComponent() {
2159 const local1 = {};
2160 const local2 = {};
2161 useMemo(() => {
2162 console.log(local1);
2163 }, [local1, local2]);
2164 }
2165 `,
2166 errors: [
2167 {
2168 message:
2169 "React Hook useMemo has an unnecessary dependency: 'local2'. " +
2170 'Either exclude it or remove the dependency array.',
2171 suggestions: [
2172 {
2173 desc: 'Update the dependencies array to be: [local1]',
2174 output: normalizeIndent`
2175 function MyComponent() {
2176 const local1 = {};
2177 const local2 = {};
2178 useMemo(() => {
2179 console.log(local1);
2180 }, [local1]);
2181 }
2182 `,
2183 },
2184 ],
2185 },
2186 ],
2187 },
2188 {
2189 code: normalizeIndent`
2190 function MyComponent() {
2191 const local1 = someFunc();
2192 function MyNestedComponent() {
2193 const local2 = {};
2194 useCallback(() => {
2195 console.log(local1);
2196 console.log(local2);
2197 }, [local1]);
2198 }
2199 }
2200 `,
2201 errors: [
2202 {
2203 message:
2204 "React Hook useCallback has a missing dependency: 'local2'. " +
2205 'Either include it or remove the dependency array. ' +
2206 "Outer scope values like 'local1' aren't valid dependencies " +
2207 "because mutating them doesn't re-render the component.",
2208 suggestions: [
2209 {
2210 desc: 'Update the dependencies array to be: [local2]',
2211 output: normalizeIndent`
2212 function MyComponent() {
2213 const local1 = someFunc();
2214 function MyNestedComponent() {
2215 const local2 = {};
2216 useCallback(() => {
2217 console.log(local1);
2218 console.log(local2);
2219 }, [local2]);
2220 }
2221 }
2222 `,
2223 },
2224 ],
2225 },
2226 ],
2227 },
2228 {
2229 code: normalizeIndent`
2230 function MyComponent() {
2231 const local = {};
2232 useEffect(() => {
2233 console.log(local);
2234 console.log(local);
2235 }, []);
2236 }
2237 `,
2238 errors: [
2239 {
2240 message:
2241 "React Hook useEffect has a missing dependency: 'local'. " +
2242 'Either include it or remove the dependency array.',
2243 suggestions: [
2244 {
2245 desc: 'Update the dependencies array to be: [local]',
2246 output: normalizeIndent`
2247 function MyComponent() {
2248 const local = {};
2249 useEffect(() => {
2250 console.log(local);
2251 console.log(local);
2252 }, [local]);
2253 }
2254 `,
2255 },
2256 ],
2257 },
2258 ],
2259 },
2260 {
2261 code: normalizeIndent`
2262 function MyComponent() {
2263 const local = {};
2264 useEffect(() => {
2265 console.log(local);
2266 console.log(local);
2267 }, [local, local]);
2268 }
2269 `,
2270 errors: [
2271 {
2272 message:
2273 "React Hook useEffect has a duplicate dependency: 'local'. " +
2274 'Either omit it or remove the dependency array.',
2275 suggestions: [
2276 {
2277 desc: 'Update the dependencies array to be: [local]',
2278 output: normalizeIndent`
2279 function MyComponent() {
2280 const local = {};
2281 useEffect(() => {
2282 console.log(local);
2283 console.log(local);
2284 }, [local]);
2285 }
2286 `,
2287 },
2288 ],
2289 },
2290 ],
2291 },
2292 {
2293 code: normalizeIndent`
2294 function MyComponent() {
2295 useCallback(() => {}, [window]);
2296 }
2297 `,
2298 errors: [
2299 {
2300 message:
2301 "React Hook useCallback has an unnecessary dependency: 'window'. " +
2302 'Either exclude it or remove the dependency array. ' +
2303 "Outer scope values like 'window' aren't valid dependencies " +
2304 "because mutating them doesn't re-render the component.",
2305 suggestions: [
2306 {
2307 desc: 'Update the dependencies array to be: []',
2308 output: normalizeIndent`
2309 function MyComponent() {
2310 useCallback(() => {}, []);
2311 }
2312 `,
2313 },
2314 ],
2315 },
2316 ],
2317 },
2318 {
2319 // It is not valid for useCallback to specify extraneous deps
2320 // because it doesn't serve as a side effect trigger unlike useEffect.
2321 code: normalizeIndent`
2322 function MyComponent(props) {
2323 let local = props.foo;
2324 useCallback(() => {}, [local]);
2325 }
2326 `,
2327 errors: [
2328 {
2329 message:
2330 "React Hook useCallback has an unnecessary dependency: 'local'. " +
2331 'Either exclude it or remove the dependency array.',
2332 suggestions: [
2333 {
2334 desc: 'Update the dependencies array to be: []',
2335 output: normalizeIndent`
2336 function MyComponent(props) {
2337 let local = props.foo;
2338 useCallback(() => {}, []);
2339 }
2340 `,
2341 },
2342 ],
2343 },
2344 ],
2345 },
2346 {
2347 code: normalizeIndent`
2348 function MyComponent({ history }) {
2349 useEffect(() => {
2350 return history.listen();
2351 }, []);
2352 }
2353 `,
2354 errors: [
2355 {
2356 message:
2357 "React Hook useEffect has a missing dependency: 'history'. " +
2358 'Either include it or remove the dependency array.',
2359 suggestions: [
2360 {
2361 desc: 'Update the dependencies array to be: [history]',
2362 output: normalizeIndent`
2363 function MyComponent({ history }) {
2364 useEffect(() => {
2365 return history.listen();
2366 }, [history]);
2367 }
2368 `,
2369 },
2370 ],
2371 },
2372 ],
2373 },
2374 {
2375 code: normalizeIndent`
2376 function MyComponent({ history }) {
2377 useEffect(() => {
2378 return [
2379 history.foo.bar[2].dobedo.listen(),
2380 history.foo.bar().dobedo.listen[2]
2381 ];
2382 }, []);
2383 }
2384 `,
2385 errors: [
2386 {
2387 message:
2388 "React Hook useEffect has a missing dependency: 'history.foo'. " +
2389 'Either include it or remove the dependency array.',
2390 suggestions: [
2391 {
2392 desc: 'Update the dependencies array to be: [history.foo]',
2393 output: normalizeIndent`
2394 function MyComponent({ history }) {
2395 useEffect(() => {
2396 return [
2397 history.foo.bar[2].dobedo.listen(),
2398 history.foo.bar().dobedo.listen[2]
2399 ];
2400 }, [history.foo]);
2401 }
2402 `,
2403 },
2404 ],
2405 },
2406 ],
2407 },
2408 {
2409 code: normalizeIndent`
2410 function MyComponent({ history }) {
2411 useEffect(() => {
2412 return [
2413 history?.foo
2414 ];
2415 }, []);
2416 }
2417 `,
2418 errors: [
2419 {
2420 message:
2421 "React Hook useEffect has a missing dependency: 'history?.foo'. " +
2422 'Either include it or remove the dependency array.',
2423 suggestions: [
2424 {
2425 desc: 'Update the dependencies array to be: [history?.foo]',
2426 output: normalizeIndent`
2427 function MyComponent({ history }) {
2428 useEffect(() => {
2429 return [
2430 history?.foo
2431 ];
2432 }, [history?.foo]);
2433 }
2434 `,
2435 },
2436 ],
2437 },
2438 ],
2439 },
2440 {
2441 code: normalizeIndent`
2442 function MyComponent() {
2443 useEffect(() => {}, ['foo']);
2444 }
2445 `,
2446 errors: [
2447 {
2448 message:
2449 // Don't assume user meant `foo` because it's not used in the effect.
2450 "The 'foo' literal is not a valid dependency because it never changes. " +
2451 'You can safely remove it.',
2452 // TODO: provide suggestion.
2453 suggestions: undefined,
2454 },
2455 ],
2456 },
2457 {
2458 code: normalizeIndent`
2459 function MyComponent({ foo, bar, baz }) {
2460 useEffect(() => {
2461 console.log(foo, bar, baz);
2462 }, ['foo', 'bar']);
2463 }
2464 `,
2465 errors: [
2466 {
2467 message:
2468 "React Hook useEffect has missing dependencies: 'bar', 'baz', and 'foo'. " +
2469 'Either include them or remove the dependency array.',
2470 suggestions: [
2471 {
2472 desc: 'Update the dependencies array to be: [bar, baz, foo]',
2473 output: normalizeIndent`
2474 function MyComponent({ foo, bar, baz }) {
2475 useEffect(() => {
2476 console.log(foo, bar, baz);
2477 }, [bar, baz, foo]);
2478 }
2479 `,
2480 },
2481 ],
2482 },
2483 {
2484 message:
2485 "The 'foo' literal is not a valid dependency because it never changes. " +
2486 'Did you mean to include foo in the array instead?',
2487 suggestions: undefined,
2488 },
2489 {
2490 message:
2491 "The 'bar' literal is not a valid dependency because it never changes. " +
2492 'Did you mean to include bar in the array instead?',
2493 suggestions: undefined,
2494 },
2495 ],
2496 },
2497 {
2498 code: normalizeIndent`
2499 function MyComponent({ foo, bar, baz }) {
2500 useEffect(() => {
2501 console.log(foo, bar, baz);
2502 }, [42, false, null]);
2503 }
2504 `,
2505 errors: [
2506 {
2507 message:
2508 "React Hook useEffect has missing dependencies: 'bar', 'baz', and 'foo'. " +
2509 'Either include them or remove the dependency array.',
2510 suggestions: [
2511 {
2512 desc: 'Update the dependencies array to be: [bar, baz, foo]',
2513 output: normalizeIndent`
2514 function MyComponent({ foo, bar, baz }) {
2515 useEffect(() => {
2516 console.log(foo, bar, baz);
2517 }, [bar, baz, foo]);
2518 }
2519 `,
2520 },
2521 ],
2522 },
2523 {
2524 message:
2525 'The 42 literal is not a valid dependency because it never changes. You can safely remove it.',
2526 suggestions: undefined,
2527 },
2528 {
2529 message:
2530 'The false literal is not a valid dependency because it never changes. You can safely remove it.',
2531 suggestions: undefined,
2532 },
2533 {
2534 message:
2535 'The null literal is not a valid dependency because it never changes. You can safely remove it.',
2536 suggestions: undefined,
2537 },
2538 ],
2539 },
2540 {
2541 code: normalizeIndent`
2542 function MyComponent() {
2543 const dependencies = [];
2544 useEffect(() => {}, dependencies);
2545 }
2546 `,
2547 errors: [
2548 {
2549 message:
2550 'React Hook useEffect was passed a dependency list that is not an ' +
2551 "array literal. This means we can't statically verify whether you've " +
2552 'passed the correct dependencies.',
2553 suggestions: undefined,
2554 },
2555 ],
2556 },
2557 {
2558 code: normalizeIndent`
2559 function MyComponent() {
2560 const local = {};
2561 const dependencies = [local];
2562 useEffect(() => {
2563 console.log(local);
2564 }, dependencies);
2565 }
2566 `,
2567 errors: [
2568 {
2569 message:
2570 'React Hook useEffect was passed a dependency list that is not an ' +
2571 "array literal. This means we can't statically verify whether you've " +
2572 'passed the correct dependencies.',
2573 // TODO: should this autofix or bail out?
2574 suggestions: undefined,
2575 },
2576 {
2577 message:
2578 "React Hook useEffect has a missing dependency: 'local'. " +
2579 'Either include it or remove the dependency array.',
2580 suggestions: [
2581 {
2582 desc: 'Update the dependencies array to be: [local]',
2583 output: normalizeIndent`
2584 function MyComponent() {
2585 const local = {};
2586 const dependencies = [local];
2587 useEffect(() => {
2588 console.log(local);
2589 }, [local]);
2590 }
2591 `,
2592 },
2593 ],
2594 },
2595 ],
2596 },
2597 {
2598 code: normalizeIndent`
2599 function MyComponent() {
2600 const local = {};
2601 const dependencies = [local];
2602 useEffect(() => {
2603 console.log(local);
2604 }, [...dependencies]);
2605 }
2606 `,
2607 errors: [
2608 {
2609 message:
2610 "React Hook useEffect has a missing dependency: 'local'. " +
2611 'Either include it or remove the dependency array.',
2612 suggestions: [
2613 {
2614 desc: 'Update the dependencies array to be: [local]',
2615 output: normalizeIndent`
2616 function MyComponent() {
2617 const local = {};
2618 const dependencies = [local];
2619 useEffect(() => {
2620 console.log(local);
2621 }, [local]);
2622 }
2623 `,
2624 },
2625 ],
2626 },
2627 {
2628 message:
2629 'React Hook useEffect has a spread element in its dependency array. ' +
2630 "This means we can't statically verify whether you've passed the " +
2631 'correct dependencies.',
2632 // TODO: should this autofix or bail out?
2633 suggestions: undefined,
2634 },
2635 ],
2636 },
2637 {
2638 code: normalizeIndent`
2639 function MyComponent() {
2640 const local = someFunc();
2641 useEffect(() => {
2642 console.log(local);
2643 }, [local, ...dependencies]);
2644 }
2645 `,
2646 errors: [
2647 {
2648 message:
2649 'React Hook useEffect has a spread element in its dependency array. ' +
2650 "This means we can't statically verify whether you've passed the " +
2651 'correct dependencies.',
2652 suggestions: undefined,
2653 },
2654 ],
2655 },
2656 {
2657 code: normalizeIndent`
2658 function MyComponent() {
2659 const local = {};
2660 useEffect(() => {
2661 console.log(local);
2662 }, [computeCacheKey(local)]);
2663 }
2664 `,
2665 errors: [
2666 {
2667 message:
2668 "React Hook useEffect has a missing dependency: 'local'. " +
2669 'Either include it or remove the dependency array.',
2670 // TODO: I'm not sure this is a good idea.
2671 // Maybe bail out?
2672 suggestions: [
2673 {
2674 desc: 'Update the dependencies array to be: [local]',
2675 output: normalizeIndent`
2676 function MyComponent() {
2677 const local = {};
2678 useEffect(() => {
2679 console.log(local);
2680 }, [local]);
2681 }
2682 `,
2683 },
2684 ],
2685 },
2686 {
2687 message:
2688 'React Hook useEffect has a complex expression in the dependency array. ' +
2689 'Extract it to a separate variable so it can be statically checked.',
2690 suggestions: undefined,
2691 },
2692 ],
2693 },
2694 {
2695 code: normalizeIndent`
2696 function MyComponent(props) {
2697 useEffect(() => {
2698 console.log(props.items[0]);
2699 }, [props.items[0]]);
2700 }
2701 `,
2702 errors: [
2703 {
2704 message:
2705 "React Hook useEffect has a missing dependency: 'props.items'. " +
2706 'Either include it or remove the dependency array.',
2707 suggestions: [
2708 {
2709 desc: 'Update the dependencies array to be: [props.items]',
2710 output: normalizeIndent`
2711 function MyComponent(props) {
2712 useEffect(() => {
2713 console.log(props.items[0]);
2714 }, [props.items]);
2715 }
2716 `,
2717 },
2718 ],
2719 },
2720 {
2721 message:
2722 'React Hook useEffect has a complex expression in the dependency array. ' +
2723 'Extract it to a separate variable so it can be statically checked.',
2724 suggestions: undefined,
2725 },
2726 ],
2727 },
2728 {
2729 code: normalizeIndent`
2730 function MyComponent(props) {
2731 useEffect(() => {
2732 console.log(props.items[0]);
2733 }, [props.items, props.items[0]]);
2734 }
2735 `,
2736 errors: [
2737 {
2738 message:
2739 'React Hook useEffect has a complex expression in the dependency array. ' +
2740 'Extract it to a separate variable so it can be statically checked.',
2741 // TODO: ideally suggestion would remove the bad expression?
2742 suggestions: undefined,
2743 },
2744 ],
2745 },
2746 {
2747 code: normalizeIndent`
2748 function MyComponent({ items }) {
2749 useEffect(() => {
2750 console.log(items[0]);
2751 }, [items[0]]);
2752 }
2753 `,
2754 errors: [
2755 {
2756 message:
2757 "React Hook useEffect has a missing dependency: 'items'. " +
2758 'Either include it or remove the dependency array.',
2759 suggestions: [
2760 {
2761 desc: 'Update the dependencies array to be: [items]',
2762 output: normalizeIndent`
2763 function MyComponent({ items }) {
2764 useEffect(() => {
2765 console.log(items[0]);
2766 }, [items]);
2767 }
2768 `,
2769 },
2770 ],
2771 },
2772 {
2773 message:
2774 'React Hook useEffect has a complex expression in the dependency array. ' +
2775 'Extract it to a separate variable so it can be statically checked.',
2776 suggestions: undefined,
2777 },
2778 ],
2779 },
2780 {
2781 code: normalizeIndent`
2782 function MyComponent({ items }) {
2783 useEffect(() => {
2784 console.log(items[0]);
2785 }, [items, items[0]]);
2786 }
2787 `,
2788 errors: [
2789 {
2790 message:
2791 'React Hook useEffect has a complex expression in the dependency array. ' +
2792 'Extract it to a separate variable so it can be statically checked.',
2793 // TODO: ideally suggeston would remove the bad expression?
2794 suggestions: undefined,
2795 },
2796 ],
2797 },
2798 {
2799 // It is not valid for useCallback to specify extraneous deps
2800 // because it doesn't serve as a side effect trigger unlike useEffect.
2801 // However, we generally allow specifying *broader* deps as escape hatch.
2802 // So while [props, props.foo] is unnecessary, 'props' wins here as the
2803 // broader one, and this is why 'props.foo' is reported as unnecessary.
2804 code: normalizeIndent`
2805 function MyComponent(props) {
2806 const local = {};
2807 useCallback(() => {
2808 console.log(props.foo);
2809 console.log(props.bar);
2810 }, [props, props.foo]);
2811 }
2812 `,
2813 errors: [
2814 {
2815 message:
2816 "React Hook useCallback has an unnecessary dependency: 'props.foo'. " +
2817 'Either exclude it or remove the dependency array.',
2818 suggestions: [
2819 {
2820 desc: 'Update the dependencies array to be: [props]',
2821 output: normalizeIndent`
2822 function MyComponent(props) {
2823 const local = {};
2824 useCallback(() => {
2825 console.log(props.foo);
2826 console.log(props.bar);
2827 }, [props]);
2828 }
2829 `,
2830 },
2831 ],
2832 },
2833 ],
2834 },
2835 {
2836 // Since we don't have 'props' in the list, we'll suggest narrow dependencies.
2837 code: normalizeIndent`
2838 function MyComponent(props) {
2839 const local = {};
2840 useCallback(() => {
2841 console.log(props.foo);
2842 console.log(props.bar);
2843 }, []);
2844 }
2845 `,
2846 errors: [
2847 {
2848 message:
2849 "React Hook useCallback has missing dependencies: 'props.bar' and 'props.foo'. " +
2850 'Either include them or remove the dependency array.',
2851 suggestions: [
2852 {
2853 desc: 'Update the dependencies array to be: [props.bar, props.foo]',
2854 output: normalizeIndent`
2855 function MyComponent(props) {
2856 const local = {};
2857 useCallback(() => {
2858 console.log(props.foo);
2859 console.log(props.bar);
2860 }, [props.bar, props.foo]);
2861 }
2862 `,
2863 },
2864 ],
2865 },
2866 ],
2867 },
2868 {
2869 // Effects are allowed to over-specify deps. We'll complain about missing
2870 // 'local', but we won't remove the already-specified 'local.id' from your list.
2871 code: normalizeIndent`
2872 function MyComponent() {
2873 const local = {id: 42};
2874 useEffect(() => {
2875 console.log(local);
2876 }, [local.id]);
2877 }
2878 `,
2879 errors: [
2880 {
2881 message:
2882 "React Hook useEffect has a missing dependency: 'local'. " +
2883 'Either include it or remove the dependency array.',
2884 suggestions: [
2885 {
2886 desc: 'Update the dependencies array to be: [local, local.id]',
2887 output: normalizeIndent`
2888 function MyComponent() {
2889 const local = {id: 42};
2890 useEffect(() => {
2891 console.log(local);
2892 }, [local, local.id]);
2893 }
2894 `,
2895 },
2896 ],
2897 },
2898 ],
2899 },
2900 {
2901 // Callbacks are not allowed to over-specify deps. So we'll complain about missing
2902 // 'local' and we will also *remove* 'local.id' from your list.
2903 code: normalizeIndent`
2904 function MyComponent() {
2905 const local = {id: 42};
2906 const fn = useCallback(() => {
2907 console.log(local);
2908 }, [local.id]);
2909 }
2910 `,
2911 errors: [
2912 {
2913 message:
2914 "React Hook useCallback has a missing dependency: 'local'. " +
2915 'Either include it or remove the dependency array.',
2916 suggestions: [
2917 {
2918 desc: 'Update the dependencies array to be: [local]',
2919 output: normalizeIndent`
2920 function MyComponent() {
2921 const local = {id: 42};
2922 const fn = useCallback(() => {
2923 console.log(local);
2924 }, [local]);
2925 }
2926 `,
2927 },
2928 ],
2929 },
2930 ],
2931 },
2932 {
2933 // Callbacks are not allowed to over-specify deps. So we'll complain about
2934 // the unnecessary 'local.id'.
2935 code: normalizeIndent`
2936 function MyComponent() {
2937 const local = {id: 42};
2938 const fn = useCallback(() => {
2939 console.log(local);
2940 }, [local.id, local]);
2941 }
2942 `,
2943 errors: [
2944 {
2945 message:
2946 "React Hook useCallback has an unnecessary dependency: 'local.id'. " +
2947 'Either exclude it or remove the dependency array.',
2948 suggestions: [
2949 {
2950 desc: 'Update the dependencies array to be: [local]',
2951 output: normalizeIndent`
2952 function MyComponent() {
2953 const local = {id: 42};
2954 const fn = useCallback(() => {
2955 console.log(local);
2956 }, [local]);
2957 }
2958 `,
2959 },
2960 ],
2961 },
2962 ],
2963 },
2964 {
2965 code: normalizeIndent`
2966 function MyComponent(props) {
2967 const fn = useCallback(() => {
2968 console.log(props.foo.bar.baz);
2969 }, []);
2970 }
2971 `,
2972 errors: [
2973 {
2974 message:
2975 "React Hook useCallback has a missing dependency: 'props.foo.bar.baz'. " +
2976 'Either include it or remove the dependency array.',
2977 suggestions: [
2978 {
2979 desc: 'Update the dependencies array to be: [props.foo.bar.baz]',
2980 output: normalizeIndent`
2981 function MyComponent(props) {
2982 const fn = useCallback(() => {
2983 console.log(props.foo.bar.baz);
2984 }, [props.foo.bar.baz]);
2985 }
2986 `,
2987 },
2988 ],
2989 },
2990 ],
2991 },
2992 {
2993 code: normalizeIndent`
2994 function MyComponent(props) {
2995 let color = {}
2996 const fn = useCallback(() => {
2997 console.log(props.foo.bar.baz);
2998 console.log(color);
2999 }, [props.foo, props.foo.bar.baz]);
3000 }
3001 `,
3002 errors: [
3003 {
3004 message:
3005 "React Hook useCallback has a missing dependency: 'color'. " +
3006 'Either include it or remove the dependency array.',
3007 suggestions: [
3008 {
3009 desc: 'Update the dependencies array to be: [color, props.foo.bar.baz]',
3010 output: normalizeIndent`
3011 function MyComponent(props) {
3012 let color = {}
3013 const fn = useCallback(() => {
3014 console.log(props.foo.bar.baz);
3015 console.log(color);
3016 }, [color, props.foo.bar.baz]);
3017 }
3018 `,
3019 },
3020 ],
3021 },
3022 ],
3023 },
3024 {
3025 // Callbacks are not allowed to over-specify deps. So one of these is extra.
3026 // However, it *is* allowed to specify broader deps then strictly necessary.
3027 // So in this case we ask you to remove 'props.foo.bar.baz' because 'props.foo'
3028 // already covers it, and having both is unnecessary.
3029 // TODO: maybe consider suggesting a narrower one by default in these cases.
3030 code: normalizeIndent`
3031 function MyComponent(props) {
3032 const fn = useCallback(() => {
3033 console.log(props.foo.bar.baz);
3034 }, [props.foo.bar.baz, props.foo]);
3035 }
3036 `,
3037 errors: [
3038 {
3039 message:
3040 "React Hook useCallback has an unnecessary dependency: 'props.foo.bar.baz'. " +
3041 'Either exclude it or remove the dependency array.',
3042 suggestions: [
3043 {
3044 desc: 'Update the dependencies array to be: [props.foo]',
3045 output: normalizeIndent`
3046 function MyComponent(props) {
3047 const fn = useCallback(() => {
3048 console.log(props.foo.bar.baz);
3049 }, [props.foo]);
3050 }
3051 `,
3052 },
3053 ],
3054 },
3055 ],
3056 },
3057 {
3058 code: normalizeIndent`
3059 function MyComponent(props) {
3060 const fn = useCallback(() => {
3061 console.log(props.foo.bar.baz);
3062 console.log(props.foo.fizz.bizz);
3063 }, []);
3064 }
3065 `,
3066 errors: [
3067 {
3068 message:
3069 "React Hook useCallback has missing dependencies: 'props.foo.bar.baz' and 'props.foo.fizz.bizz'. " +
3070 'Either include them or remove the dependency array.',
3071 suggestions: [
3072 {
3073 desc: 'Update the dependencies array to be: [props.foo.bar.baz, props.foo.fizz.bizz]',
3074 output: normalizeIndent`
3075 function MyComponent(props) {
3076 const fn = useCallback(() => {
3077 console.log(props.foo.bar.baz);
3078 console.log(props.foo.fizz.bizz);
3079 }, [props.foo.bar.baz, props.foo.fizz.bizz]);
3080 }
3081 `,
3082 },
3083 ],
3084 },
3085 ],
3086 },
3087 {
3088 // Normally we allow specifying deps too broadly.
3089 // So we'd be okay if 'props.foo.bar' was there rather than 'props.foo.bar.baz'.
3090 // However, 'props.foo.bar.baz' is missing. So we know there is a mistake.
3091 // When we're sure there is a mistake, for callbacks we will rebuild the list
3092 // from scratch. This will set the user on a better path by default.
3093 // This is why we end up with just 'props.foo.bar', and not them both.
3094 code: normalizeIndent`
3095 function MyComponent(props) {
3096 const fn = useCallback(() => {
3097 console.log(props.foo.bar);
3098 }, [props.foo.bar.baz]);
3099 }
3100 `,
3101 errors: [
3102 {
3103 message:
3104 "React Hook useCallback has a missing dependency: 'props.foo.bar'. " +
3105 'Either include it or remove the dependency array.',
3106 suggestions: [
3107 {
3108 desc: 'Update the dependencies array to be: [props.foo.bar]',
3109 output: normalizeIndent`
3110 function MyComponent(props) {
3111 const fn = useCallback(() => {
3112 console.log(props.foo.bar);
3113 }, [props.foo.bar]);
3114 }
3115 `,
3116 },
3117 ],
3118 },
3119 ],
3120 },
3121 {
3122 code: normalizeIndent`
3123 function MyComponent(props) {
3124 const fn = useCallback(() => {
3125 console.log(props);
3126 console.log(props.hello);
3127 }, [props.foo.bar.baz]);
3128 }
3129 `,
3130 errors: [
3131 {
3132 message:
3133 "React Hook useCallback has a missing dependency: 'props'. " +
3134 'Either include it or remove the dependency array.',
3135 suggestions: [
3136 {
3137 desc: 'Update the dependencies array to be: [props]',
3138 output: normalizeIndent`
3139 function MyComponent(props) {
3140 const fn = useCallback(() => {
3141 console.log(props);
3142 console.log(props.hello);
3143 }, [props]);
3144 }
3145 `,
3146 },
3147 ],
3148 },
3149 ],
3150 },
3151 {
3152 code: normalizeIndent`
3153 function MyComponent() {
3154 const local = {};
3155 useEffect(() => {
3156 console.log(local);
3157 }, [local, local]);
3158 }
3159 `,
3160 errors: [
3161 {
3162 message:
3163 "React Hook useEffect has a duplicate dependency: 'local'. " +
3164 'Either omit it or remove the dependency array.',
3165 suggestions: [
3166 {
3167 desc: 'Update the dependencies array to be: [local]',
3168 output: normalizeIndent`
3169 function MyComponent() {
3170 const local = {};
3171 useEffect(() => {
3172 console.log(local);
3173 }, [local]);
3174 }
3175 `,
3176 },
3177 ],
3178 },
3179 ],
3180 },
3181 {
3182 code: normalizeIndent`
3183 function MyComponent() {
3184 const local1 = {};
3185 useCallback(() => {
3186 const local1 = {};
3187 console.log(local1);
3188 }, [local1]);
3189 }
3190 `,
3191 errors: [
3192 {
3193 message:
3194 "React Hook useCallback has an unnecessary dependency: 'local1'. " +
3195 'Either exclude it or remove the dependency array.',
3196 suggestions: [
3197 {
3198 desc: 'Update the dependencies array to be: []',
3199 output: normalizeIndent`
3200 function MyComponent() {
3201 const local1 = {};
3202 useCallback(() => {
3203 const local1 = {};
3204 console.log(local1);
3205 }, []);
3206 }
3207 `,
3208 },
3209 ],
3210 },
3211 ],
3212 },
3213 {
3214 code: normalizeIndent`
3215 function MyComponent() {
3216 const local1 = {};
3217 useCallback(() => {}, [local1]);
3218 }
3219 `,
3220 errors: [
3221 {
3222 message:
3223 "React Hook useCallback has an unnecessary dependency: 'local1'. " +
3224 'Either exclude it or remove the dependency array.',
3225 suggestions: [
3226 {
3227 desc: 'Update the dependencies array to be: []',
3228 output: normalizeIndent`
3229 function MyComponent() {
3230 const local1 = {};
3231 useCallback(() => {}, []);
3232 }
3233 `,
3234 },
3235 ],
3236 },
3237 ],
3238 },
3239 {
3240 code: normalizeIndent`
3241 function MyComponent(props) {
3242 useEffect(() => {
3243 console.log(props.foo);
3244 }, []);
3245 }
3246 `,
3247 errors: [
3248 {
3249 message:
3250 "React Hook useEffect has a missing dependency: 'props.foo'. " +
3251 'Either include it or remove the dependency array.',
3252 suggestions: [
3253 {
3254 desc: 'Update the dependencies array to be: [props.foo]',
3255 output: normalizeIndent`
3256 function MyComponent(props) {
3257 useEffect(() => {
3258 console.log(props.foo);
3259 }, [props.foo]);
3260 }
3261 `,
3262 },
3263 ],
3264 },
3265 ],
3266 },
3267 {
3268 code: normalizeIndent`
3269 function MyComponent(props) {
3270 useEffect(() => {
3271 console.log(props.foo);
3272 console.log(props.bar);
3273 }, []);
3274 }
3275 `,
3276 errors: [
3277 {
3278 message:
3279 "React Hook useEffect has missing dependencies: 'props.bar' and 'props.foo'. " +
3280 'Either include them or remove the dependency array.',
3281 suggestions: [
3282 {
3283 desc: 'Update the dependencies array to be: [props.bar, props.foo]',
3284 output: normalizeIndent`
3285 function MyComponent(props) {
3286 useEffect(() => {
3287 console.log(props.foo);
3288 console.log(props.bar);
3289 }, [props.bar, props.foo]);
3290 }
3291 `,
3292 },
3293 ],
3294 },
3295 ],
3296 },
3297 {
3298 code: normalizeIndent`
3299 function MyComponent(props) {
3300 let a, b, c, d, e, f, g;
3301 useEffect(() => {
3302 console.log(b, e, d, c, a, g, f);
3303 }, [c, a, g]);
3304 }
3305 `,
3306 errors: [
3307 {
3308 message:
3309 "React Hook useEffect has missing dependencies: 'b', 'd', 'e', and 'f'. " +
3310 'Either include them or remove the dependency array.',
3311 // Don't alphabetize if it wasn't alphabetized in the first place.
3312 suggestions: [
3313 {
3314 desc: 'Update the dependencies array to be: [c, a, g, b, e, d, f]',
3315 output: normalizeIndent`
3316 function MyComponent(props) {
3317 let a, b, c, d, e, f, g;
3318 useEffect(() => {
3319 console.log(b, e, d, c, a, g, f);
3320 }, [c, a, g, b, e, d, f]);
3321 }
3322 `,
3323 },
3324 ],
3325 },
3326 ],
3327 },
3328 {
3329 code: normalizeIndent`
3330 function MyComponent(props) {
3331 let a, b, c, d, e, f, g;
3332 useEffect(() => {
3333 console.log(b, e, d, c, a, g, f);
3334 }, [a, c, g]);
3335 }
3336 `,
3337 errors: [
3338 {
3339 message:
3340 "React Hook useEffect has missing dependencies: 'b', 'd', 'e', and 'f'. " +
3341 'Either include them or remove the dependency array.',
3342 // Alphabetize if it was alphabetized.
3343 suggestions: [
3344 {
3345 desc: 'Update the dependencies array to be: [a, b, c, d, e, f, g]',
3346 output: normalizeIndent`
3347 function MyComponent(props) {
3348 let a, b, c, d, e, f, g;
3349 useEffect(() => {
3350 console.log(b, e, d, c, a, g, f);
3351 }, [a, b, c, d, e, f, g]);
3352 }
3353 `,
3354 },
3355 ],
3356 },
3357 ],
3358 },
3359 {
3360 code: normalizeIndent`
3361 function MyComponent(props) {
3362 let a, b, c, d, e, f, g;
3363 useEffect(() => {
3364 console.log(b, e, d, c, a, g, f);
3365 }, []);
3366 }
3367 `,
3368 errors: [
3369 {
3370 message:
3371 "React Hook useEffect has missing dependencies: 'a', 'b', 'c', 'd', 'e', 'f', and 'g'. " +
3372 'Either include them or remove the dependency array.',
3373 // Alphabetize if it was empty.
3374 suggestions: [
3375 {
3376 desc: 'Update the dependencies array to be: [a, b, c, d, e, f, g]',
3377 output: normalizeIndent`
3378 function MyComponent(props) {
3379 let a, b, c, d, e, f, g;
3380 useEffect(() => {
3381 console.log(b, e, d, c, a, g, f);
3382 }, [a, b, c, d, e, f, g]);
3383 }
3384 `,
3385 },
3386 ],
3387 },
3388 ],
3389 },
3390 {
3391 code: normalizeIndent`
3392 function MyComponent(props) {
3393 const local = {};
3394 useEffect(() => {
3395 console.log(props.foo);
3396 console.log(props.bar);
3397 console.log(local);
3398 }, []);
3399 }
3400 `,
3401 errors: [
3402 {
3403 message:
3404 "React Hook useEffect has missing dependencies: 'local', 'props.bar', and 'props.foo'. " +
3405 'Either include them or remove the dependency array.',
3406 suggestions: [
3407 {
3408 desc: 'Update the dependencies array to be: [local, props.bar, props.foo]',
3409 output: normalizeIndent`
3410 function MyComponent(props) {
3411 const local = {};
3412 useEffect(() => {
3413 console.log(props.foo);
3414 console.log(props.bar);
3415 console.log(local);
3416 }, [local, props.bar, props.foo]);
3417 }
3418 `,
3419 },
3420 ],
3421 },
3422 ],
3423 },
3424 {
3425 code: normalizeIndent`
3426 function MyComponent(props) {
3427 const local = {};
3428 useEffect(() => {
3429 console.log(props.foo);
3430 console.log(props.bar);
3431 console.log(local);
3432 }, [props]);
3433 }
3434 `,
3435 errors: [
3436 {
3437 message:
3438 "React Hook useEffect has a missing dependency: 'local'. " +
3439 'Either include it or remove the dependency array.',
3440 suggestions: [
3441 {
3442 desc: 'Update the dependencies array to be: [local, props]',
3443 output: normalizeIndent`
3444 function MyComponent(props) {
3445 const local = {};
3446 useEffect(() => {
3447 console.log(props.foo);
3448 console.log(props.bar);
3449 console.log(local);
3450 }, [local, props]);
3451 }
3452 `,
3453 },
3454 ],
3455 },
3456 ],
3457 },
3458 {
3459 code: normalizeIndent`
3460 function MyComponent(props) {
3461 useEffect(() => {
3462 console.log(props.foo);
3463 }, []);
3464 useCallback(() => {
3465 console.log(props.foo);
3466 }, []);
3467 useMemo(() => {
3468 console.log(props.foo);
3469 }, []);
3470 React.useEffect(() => {
3471 console.log(props.foo);
3472 }, []);
3473 React.useCallback(() => {
3474 console.log(props.foo);
3475 }, []);
3476 React.useMemo(() => {
3477 console.log(props.foo);
3478 }, []);
3479 React.notReactiveHook(() => {
3480 console.log(props.foo);
3481 }, []);
3482 }
3483 `,
3484 errors: [
3485 {
3486 message:
3487 "React Hook useEffect has a missing dependency: 'props.foo'. " +
3488 'Either include it or remove the dependency array.',
3489 suggestions: [
3490 {
3491 desc: 'Update the dependencies array to be: [props.foo]',
3492 output: normalizeIndent`
3493 function MyComponent(props) {
3494 useEffect(() => {
3495 console.log(props.foo);
3496 }, [props.foo]);
3497 useCallback(() => {
3498 console.log(props.foo);
3499 }, []);
3500 useMemo(() => {
3501 console.log(props.foo);
3502 }, []);
3503 React.useEffect(() => {
3504 console.log(props.foo);
3505 }, []);
3506 React.useCallback(() => {
3507 console.log(props.foo);
3508 }, []);
3509 React.useMemo(() => {
3510 console.log(props.foo);
3511 }, []);
3512 React.notReactiveHook(() => {
3513 console.log(props.foo);
3514 }, []);
3515 }
3516 `,
3517 },
3518 ],
3519 },
3520 {
3521 message:
3522 "React Hook useCallback has a missing dependency: 'props.foo'. " +
3523 'Either include it or remove the dependency array.',
3524 suggestions: [
3525 {
3526 desc: 'Update the dependencies array to be: [props.foo]',
3527 output: normalizeIndent`
3528 function MyComponent(props) {
3529 useEffect(() => {
3530 console.log(props.foo);
3531 }, []);
3532 useCallback(() => {
3533 console.log(props.foo);
3534 }, [props.foo]);
3535 useMemo(() => {
3536 console.log(props.foo);
3537 }, []);
3538 React.useEffect(() => {
3539 console.log(props.foo);
3540 }, []);
3541 React.useCallback(() => {
3542 console.log(props.foo);
3543 }, []);
3544 React.useMemo(() => {
3545 console.log(props.foo);
3546 }, []);
3547 React.notReactiveHook(() => {
3548 console.log(props.foo);
3549 }, []);
3550 }
3551 `,
3552 },
3553 ],
3554 },
3555 {
3556 message:
3557 "React Hook useMemo has a missing dependency: 'props.foo'. " +
3558 'Either include it or remove the dependency array.',
3559 suggestions: [
3560 {
3561 desc: 'Update the dependencies array to be: [props.foo]',
3562 output: normalizeIndent`
3563 function MyComponent(props) {
3564 useEffect(() => {
3565 console.log(props.foo);
3566 }, []);
3567 useCallback(() => {
3568 console.log(props.foo);
3569 }, []);
3570 useMemo(() => {
3571 console.log(props.foo);
3572 }, [props.foo]);
3573 React.useEffect(() => {
3574 console.log(props.foo);
3575 }, []);
3576 React.useCallback(() => {
3577 console.log(props.foo);
3578 }, []);
3579 React.useMemo(() => {
3580 console.log(props.foo);
3581 }, []);
3582 React.notReactiveHook(() => {
3583 console.log(props.foo);
3584 }, []);
3585 }
3586 `,
3587 },
3588 ],
3589 },
3590 {
3591 message:
3592 "React Hook React.useEffect has a missing dependency: 'props.foo'. " +
3593 'Either include it or remove the dependency array.',
3594 suggestions: [
3595 {
3596 desc: 'Update the dependencies array to be: [props.foo]',
3597 output: normalizeIndent`
3598 function MyComponent(props) {
3599 useEffect(() => {
3600 console.log(props.foo);
3601 }, []);
3602 useCallback(() => {
3603 console.log(props.foo);
3604 }, []);
3605 useMemo(() => {
3606 console.log(props.foo);
3607 }, []);
3608 React.useEffect(() => {
3609 console.log(props.foo);
3610 }, [props.foo]);
3611 React.useCallback(() => {
3612 console.log(props.foo);
3613 }, []);
3614 React.useMemo(() => {
3615 console.log(props.foo);
3616 }, []);
3617 React.notReactiveHook(() => {
3618 console.log(props.foo);
3619 }, []);
3620 }
3621 `,
3622 },
3623 ],
3624 },
3625 {
3626 message:
3627 "React Hook React.useCallback has a missing dependency: 'props.foo'. " +
3628 'Either include it or remove the dependency array.',
3629 suggestions: [
3630 {
3631 desc: 'Update the dependencies array to be: [props.foo]',
3632 output: normalizeIndent`
3633 function MyComponent(props) {
3634 useEffect(() => {
3635 console.log(props.foo);
3636 }, []);
3637 useCallback(() => {
3638 console.log(props.foo);
3639 }, []);
3640 useMemo(() => {
3641 console.log(props.foo);
3642 }, []);
3643 React.useEffect(() => {
3644 console.log(props.foo);
3645 }, []);
3646 React.useCallback(() => {
3647 console.log(props.foo);
3648 }, [props.foo]);
3649 React.useMemo(() => {
3650 console.log(props.foo);
3651 }, []);
3652 React.notReactiveHook(() => {
3653 console.log(props.foo);
3654 }, []);
3655 }
3656 `,
3657 },
3658 ],
3659 },
3660 {
3661 message:
3662 "React Hook React.useMemo has a missing dependency: 'props.foo'. " +
3663 'Either include it or remove the dependency array.',
3664 suggestions: [
3665 {
3666 desc: 'Update the dependencies array to be: [props.foo]',
3667 output: normalizeIndent`
3668 function MyComponent(props) {
3669 useEffect(() => {
3670 console.log(props.foo);
3671 }, []);
3672 useCallback(() => {
3673 console.log(props.foo);
3674 }, []);
3675 useMemo(() => {
3676 console.log(props.foo);
3677 }, []);
3678 React.useEffect(() => {
3679 console.log(props.foo);
3680 }, []);
3681 React.useCallback(() => {
3682 console.log(props.foo);
3683 }, []);
3684 React.useMemo(() => {
3685 console.log(props.foo);
3686 }, [props.foo]);
3687 React.notReactiveHook(() => {
3688 console.log(props.foo);
3689 }, []);
3690 }
3691 `,
3692 },
3693 ],
3694 },
3695 ],
3696 },
3697 {
3698 code: normalizeIndent`
3699 function MyComponent(props) {
3700 useCustomEffect(() => {
3701 console.log(props.foo);
3702 }, []);
3703 useEffect(() => {
3704 console.log(props.foo);
3705 }, []);
3706 React.useEffect(() => {
3707 console.log(props.foo);
3708 }, []);
3709 React.useCustomEffect(() => {
3710 console.log(props.foo);
3711 }, []);
3712 }
3713 `,
3714 options: [{additionalHooks: 'useCustomEffect'}],
3715 errors: [
3716 {
3717 message:
3718 "React Hook useCustomEffect has a missing dependency: 'props.foo'. " +
3719 'Either include it or remove the dependency array.',
3720 suggestions: [
3721 {
3722 desc: 'Update the dependencies array to be: [props.foo]',
3723 output: normalizeIndent`
3724 function MyComponent(props) {
3725 useCustomEffect(() => {
3726 console.log(props.foo);
3727 }, [props.foo]);
3728 useEffect(() => {
3729 console.log(props.foo);
3730 }, []);
3731 React.useEffect(() => {
3732 console.log(props.foo);
3733 }, []);
3734 React.useCustomEffect(() => {
3735 console.log(props.foo);
3736 }, []);
3737 }
3738 `,
3739 },
3740 ],
3741 },
3742 {
3743 message:
3744 "React Hook useEffect has a missing dependency: 'props.foo'. " +
3745 'Either include it or remove the dependency array.',
3746 suggestions: [
3747 {
3748 desc: 'Update the dependencies array to be: [props.foo]',
3749 output: normalizeIndent`
3750 function MyComponent(props) {
3751 useCustomEffect(() => {
3752 console.log(props.foo);
3753 }, []);
3754 useEffect(() => {
3755 console.log(props.foo);
3756 }, [props.foo]);
3757 React.useEffect(() => {
3758 console.log(props.foo);
3759 }, []);
3760 React.useCustomEffect(() => {
3761 console.log(props.foo);
3762 }, []);
3763 }
3764 `,
3765 },
3766 ],
3767 },
3768 {
3769 message:
3770 "React Hook React.useEffect has a missing dependency: 'props.foo'. " +
3771 'Either include it or remove the dependency array.',
3772 suggestions: [
3773 {
3774 desc: 'Update the dependencies array to be: [props.foo]',
3775 output: normalizeIndent`
3776 function MyComponent(props) {
3777 useCustomEffect(() => {
3778 console.log(props.foo);
3779 }, []);
3780 useEffect(() => {
3781 console.log(props.foo);
3782 }, []);
3783 React.useEffect(() => {
3784 console.log(props.foo);
3785 }, [props.foo]);
3786 React.useCustomEffect(() => {
3787 console.log(props.foo);
3788 }, []);
3789 }
3790 `,
3791 },
3792 ],
3793 },
3794 ],
3795 },
3796 {
3797 // Test settings-based additionalHooks - should detect missing dependency
3798 code: normalizeIndent`
3799 function MyComponent(props) {
3800 useCustomEffect(() => {
3801 console.log(props.foo);
3802 }, []);
3803 }
3804 `,
3805 settings: {
3806 'react-hooks': {
3807 additionalEffectHooks: 'useCustomEffect',
3808 },
3809 },
3810 errors: [
3811 {
3812 message:
3813 "React Hook useCustomEffect has a missing dependency: 'props.foo'. " +
3814 'Either include it or remove the dependency array.',
3815 suggestions: [
3816 {
3817 desc: 'Update the dependencies array to be: [props.foo]',
3818 output: normalizeIndent`
3819 function MyComponent(props) {
3820 useCustomEffect(() => {
3821 console.log(props.foo);
3822 }, [props.foo]);
3823 }
3824 `,
3825 },
3826 ],
3827 },
3828 ],
3829 },
3830 {
3831 code: normalizeIndent`
3832 function MyComponent() {
3833 const local = {};
3834 useEffect(() => {
3835 console.log(local);
3836 }, [a ? local : b]);
3837 }
3838 `,
3839 errors: [
3840 {
3841 message:
3842 "React Hook useEffect has a missing dependency: 'local'. " +
3843 'Either include it or remove the dependency array.',
3844 // TODO: should we bail out instead?
3845 suggestions: [
3846 {
3847 desc: 'Update the dependencies array to be: [local]',
3848 output: normalizeIndent`
3849 function MyComponent() {
3850 const local = {};
3851 useEffect(() => {
3852 console.log(local);
3853 }, [local]);
3854 }
3855 `,
3856 },
3857 ],
3858 },
3859 {
3860 message:
3861 'React Hook useEffect has a complex expression in the dependency array. ' +
3862 'Extract it to a separate variable so it can be statically checked.',
3863 suggestions: undefined,
3864 },
3865 ],
3866 },
3867 {
3868 code: normalizeIndent`
3869 function MyComponent() {
3870 const local = {};
3871 useEffect(() => {
3872 console.log(local);
3873 }, [a && local]);
3874 }
3875 `,
3876 errors: [
3877 {
3878 message:
3879 "React Hook useEffect has a missing dependency: 'local'. " +
3880 'Either include it or remove the dependency array.',
3881 // TODO: should we bail out instead?
3882 suggestions: [
3883 {
3884 desc: 'Update the dependencies array to be: [local]',
3885 output: normalizeIndent`
3886 function MyComponent() {
3887 const local = {};
3888 useEffect(() => {
3889 console.log(local);
3890 }, [local]);
3891 }
3892 `,
3893 },
3894 ],
3895 },
3896 {
3897 message:
3898 'React Hook useEffect has a complex expression in the dependency array. ' +
3899 'Extract it to a separate variable so it can be statically checked.',
3900 suggestions: undefined,
3901 },
3902 ],
3903 },
3904 {
3905 code: normalizeIndent`
3906 function MyComponent(props) {
3907 useEffect(() => {}, [props?.attribute.method()]);
3908 }
3909 `,
3910 errors: [
3911 {
3912 message:
3913 'React Hook useEffect has a complex expression in the dependency array. ' +
3914 'Extract it to a separate variable so it can be statically checked.',
3915 suggestions: undefined,
3916 },
3917 ],
3918 },
3919 {
3920 code: normalizeIndent`
3921 function MyComponent(props) {
3922 useEffect(() => {}, [props.method()]);
3923 }
3924 `,
3925 errors: [
3926 {
3927 message:
3928 'React Hook useEffect has a complex expression in the dependency array. ' +
3929 'Extract it to a separate variable so it can be statically checked.',
3930 suggestions: undefined,
3931 },
3932 ],
3933 },
3934 {
3935 code: normalizeIndent`
3936 function MyComponent() {
3937 const ref = useRef();
3938 const [state, setState] = useState();
3939 useEffect(() => {
3940 ref.current = {};
3941 setState(state + 1);
3942 }, []);
3943 }
3944 `,
3945 errors: [
3946 {
3947 message:
3948 "React Hook useEffect has a missing dependency: 'state'. " +
3949 'Either include it or remove the dependency array. ' +
3950 `You can also do a functional update 'setState(s => ...)' ` +
3951 `if you only need 'state' in the 'setState' call.`,
3952 suggestions: [
3953 {
3954 desc: 'Update the dependencies array to be: [state]',
3955 output: normalizeIndent`
3956 function MyComponent() {
3957 const ref = useRef();
3958 const [state, setState] = useState();
3959 useEffect(() => {
3960 ref.current = {};
3961 setState(state + 1);
3962 }, [state]);
3963 }
3964 `,
3965 },
3966 ],
3967 },
3968 ],
3969 },
3970 {
3971 code: normalizeIndent`
3972 function MyComponent() {
3973 const ref = useRef();
3974 const [state, setState] = useState();
3975 useEffect(() => {
3976 ref.current = {};
3977 setState(state + 1);
3978 }, [ref]);
3979 }
3980 `,
3981 errors: [
3982 {
3983 message:
3984 "React Hook useEffect has a missing dependency: 'state'. " +
3985 'Either include it or remove the dependency array. ' +
3986 `You can also do a functional update 'setState(s => ...)' ` +
3987 `if you only need 'state' in the 'setState' call.`,
3988 // We don't ask to remove static deps but don't add them either.
3989 // Don't suggest removing "ref" (it's fine either way)
3990 // but *do* add "state". *Don't* add "setState" ourselves.
3991 suggestions: [
3992 {
3993 desc: 'Update the dependencies array to be: [ref, state]',
3994 output: normalizeIndent`
3995 function MyComponent() {
3996 const ref = useRef();
3997 const [state, setState] = useState();
3998 useEffect(() => {
3999 ref.current = {};
4000 setState(state + 1);
4001 }, [ref, state]);
4002 }
4003 `,
4004 },
4005 ],
4006 },
4007 ],
4008 },
4009 {
4010 code: normalizeIndent`
4011 function MyComponent(props) {
4012 const ref1 = useRef();
4013 const ref2 = useRef();
4014 useEffect(() => {
4015 ref1.current.focus();
4016 console.log(ref2.current.textContent);
4017 alert(props.someOtherRefs.current.innerHTML);
4018 fetch(props.color);
4019 }, []);
4020 }
4021 `,
4022 errors: [
4023 {
4024 message:
4025 "React Hook useEffect has missing dependencies: 'props.color' and 'props.someOtherRefs'. " +
4026 'Either include them or remove the dependency array.',
4027 suggestions: [
4028 {
4029 desc: 'Update the dependencies array to be: [props.color, props.someOtherRefs]',
4030 output: normalizeIndent`
4031 function MyComponent(props) {
4032 const ref1 = useRef();
4033 const ref2 = useRef();
4034 useEffect(() => {
4035 ref1.current.focus();
4036 console.log(ref2.current.textContent);
4037 alert(props.someOtherRefs.current.innerHTML);
4038 fetch(props.color);
4039 }, [props.color, props.someOtherRefs]);
4040 }
4041 `,
4042 },
4043 ],
4044 },
4045 ],
4046 },
4047 {
4048 code: normalizeIndent`
4049 function MyComponent(props) {
4050 const ref1 = useRef();
4051 const ref2 = useRef();
4052 useEffect(() => {
4053 ref1.current.focus();
4054 console.log(ref2.current.textContent);
4055 alert(props.someOtherRefs.current.innerHTML);
4056 fetch(props.color);
4057 }, [ref1.current, ref2.current, props.someOtherRefs, props.color]);
4058 }
4059 `,
4060 errors: [
4061 {
4062 message:
4063 "React Hook useEffect has unnecessary dependencies: 'ref1.current' and 'ref2.current'. " +
4064 'Either exclude them or remove the dependency array. ' +
4065 "Mutable values like 'ref1.current' aren't valid dependencies " +
4066 "because mutating them doesn't re-render the component.",
4067 suggestions: [
4068 {
4069 desc: 'Update the dependencies array to be: [props.someOtherRefs, props.color]',
4070 output: normalizeIndent`
4071 function MyComponent(props) {
4072 const ref1 = useRef();
4073 const ref2 = useRef();
4074 useEffect(() => {
4075 ref1.current.focus();
4076 console.log(ref2.current.textContent);
4077 alert(props.someOtherRefs.current.innerHTML);
4078 fetch(props.color);
4079 }, [props.someOtherRefs, props.color]);
4080 }
4081 `,
4082 },
4083 ],
4084 },
4085 ],
4086 },
4087 {
4088 code: normalizeIndent`
4089 function MyComponent(props) {
4090 const ref1 = useRef();
4091 const ref2 = useRef();
4092 useEffect(() => {
4093 ref1?.current?.focus();
4094 console.log(ref2?.current?.textContent);
4095 alert(props.someOtherRefs.current.innerHTML);
4096 fetch(props.color);
4097 }, [ref1?.current, ref2?.current, props.someOtherRefs, props.color]);
4098 }
4099 `,
4100 errors: [
4101 {
4102 message:
4103 "React Hook useEffect has unnecessary dependencies: 'ref1.current' and 'ref2.current'. " +
4104 'Either exclude them or remove the dependency array. ' +
4105 "Mutable values like 'ref1.current' aren't valid dependencies " +
4106 "because mutating them doesn't re-render the component.",
4107 suggestions: [
4108 {
4109 desc: 'Update the dependencies array to be: [props.someOtherRefs, props.color]',
4110 output: normalizeIndent`
4111 function MyComponent(props) {
4112 const ref1 = useRef();
4113 const ref2 = useRef();
4114 useEffect(() => {
4115 ref1?.current?.focus();
4116 console.log(ref2?.current?.textContent);
4117 alert(props.someOtherRefs.current.innerHTML);
4118 fetch(props.color);
4119 }, [props.someOtherRefs, props.color]);
4120 }
4121 `,
4122 },
4123 ],
4124 },
4125 ],
4126 },
4127 {
4128 code: normalizeIndent`
4129 function MyComponent() {
4130 const ref = useRef();
4131 useEffect(() => {
4132 console.log(ref.current);
4133 }, [ref.current]);
4134 }
4135 `,
4136 errors: [
4137 {
4138 message:
4139 "React Hook useEffect has an unnecessary dependency: 'ref.current'. " +
4140 'Either exclude it or remove the dependency array. ' +
4141 "Mutable values like 'ref.current' aren't valid dependencies " +
4142 "because mutating them doesn't re-render the component.",
4143 suggestions: [
4144 {
4145 desc: 'Update the dependencies array to be: []',
4146 output: normalizeIndent`
4147 function MyComponent() {
4148 const ref = useRef();
4149 useEffect(() => {
4150 console.log(ref.current);
4151 }, []);
4152 }
4153 `,
4154 },
4155 ],
4156 },
4157 ],
4158 },
4159 {
4160 code: normalizeIndent`
4161 function MyComponent({ activeTab }) {
4162 const ref1 = useRef();
4163 const ref2 = useRef();
4164 useEffect(() => {
4165 ref1.current.scrollTop = 0;
4166 ref2.current.scrollTop = 0;
4167 }, [ref1.current, ref2.current, activeTab]);
4168 }
4169 `,
4170 errors: [
4171 {
4172 message:
4173 "React Hook useEffect has unnecessary dependencies: 'ref1.current' and 'ref2.current'. " +
4174 'Either exclude them or remove the dependency array. ' +
4175 "Mutable values like 'ref1.current' aren't valid dependencies " +
4176 "because mutating them doesn't re-render the component.",
4177 suggestions: [
4178 {
4179 desc: 'Update the dependencies array to be: [activeTab]',
4180 output: normalizeIndent`
4181 function MyComponent({ activeTab }) {
4182 const ref1 = useRef();
4183 const ref2 = useRef();
4184 useEffect(() => {
4185 ref1.current.scrollTop = 0;
4186 ref2.current.scrollTop = 0;
4187 }, [activeTab]);
4188 }
4189 `,
4190 },
4191 ],
4192 },
4193 ],
4194 },
4195 {
4196 code: normalizeIndent`
4197 function MyComponent({ activeTab, initY }) {
4198 const ref1 = useRef();
4199 const ref2 = useRef();
4200 const fn = useCallback(() => {
4201 ref1.current.scrollTop = initY;
4202 ref2.current.scrollTop = initY;
4203 }, [ref1.current, ref2.current, activeTab, initY]);
4204 }
4205 `,
4206 errors: [
4207 {
4208 message:
4209 "React Hook useCallback has unnecessary dependencies: 'activeTab', 'ref1.current', and 'ref2.current'. " +
4210 'Either exclude them or remove the dependency array. ' +
4211 "Mutable values like 'ref1.current' aren't valid dependencies " +
4212 "because mutating them doesn't re-render the component.",
4213 suggestions: [
4214 {
4215 desc: 'Update the dependencies array to be: [initY]',
4216 output: normalizeIndent`
4217 function MyComponent({ activeTab, initY }) {
4218 const ref1 = useRef();
4219 const ref2 = useRef();
4220 const fn = useCallback(() => {
4221 ref1.current.scrollTop = initY;
4222 ref2.current.scrollTop = initY;
4223 }, [initY]);
4224 }
4225 `,
4226 },
4227 ],
4228 },
4229 ],
4230 },
4231 {
4232 code: normalizeIndent`
4233 function MyComponent() {
4234 const ref = useRef();
4235 useEffect(() => {
4236 console.log(ref.current);
4237 }, [ref.current, ref]);
4238 }
4239 `,
4240 errors: [
4241 {
4242 message:
4243 "React Hook useEffect has an unnecessary dependency: 'ref.current'. " +
4244 'Either exclude it or remove the dependency array. ' +
4245 "Mutable values like 'ref.current' aren't valid dependencies " +
4246 "because mutating them doesn't re-render the component.",
4247 suggestions: [
4248 {
4249 desc: 'Update the dependencies array to be: [ref]',
4250 output: normalizeIndent`
4251 function MyComponent() {
4252 const ref = useRef();
4253 useEffect(() => {
4254 console.log(ref.current);
4255 }, [ref]);
4256 }
4257 `,
4258 },
4259 ],
4260 },
4261 ],
4262 },
4263 {
4264 code: normalizeIndent`
4265 const MyComponent = forwardRef((props, ref) => {
4266 useImperativeHandle(ref, () => ({
4267 focus() {
4268 alert(props.hello);
4269 }
4270 }), [])
4271 });
4272 `,
4273 errors: [
4274 {
4275 message:
4276 "React Hook useImperativeHandle has a missing dependency: 'props.hello'. " +
4277 'Either include it or remove the dependency array.',
4278 suggestions: [
4279 {
4280 desc: 'Update the dependencies array to be: [props.hello]',
4281 output: normalizeIndent`
4282 const MyComponent = forwardRef((props, ref) => {
4283 useImperativeHandle(ref, () => ({
4284 focus() {
4285 alert(props.hello);
4286 }
4287 }), [props.hello])
4288 });
4289 `,
4290 },
4291 ],
4292 },
4293 ],
4294 },
4295 {
4296 code: normalizeIndent`
4297 function MyComponent(props) {
4298 useEffect(() => {
4299 if (props.onChange) {
4300 props.onChange();
4301 }
4302 }, []);
4303 }
4304 `,
4305 errors: [
4306 {
4307 message:
4308 "React Hook useEffect has a missing dependency: 'props'. " +
4309 'Either include it or remove the dependency array. ' +
4310 `However, 'props' will change when *any* prop changes, so the ` +
4311 `preferred fix is to destructure the 'props' object outside ` +
4312 `of the useEffect call and refer to those specific ` +
4313 `props inside useEffect.`,
4314 suggestions: [
4315 {
4316 desc: 'Update the dependencies array to be: [props]',
4317 output: normalizeIndent`
4318 function MyComponent(props) {
4319 useEffect(() => {
4320 if (props.onChange) {
4321 props.onChange();
4322 }
4323 }, [props]);
4324 }
4325 `,
4326 },
4327 ],
4328 },
4329 ],
4330 },
4331 {
4332 code: normalizeIndent`
4333 function MyComponent(props) {
4334 useEffect(() => {
4335 if (props?.onChange) {
4336 props?.onChange();
4337 }
4338 }, []);
4339 }
4340 `,
4341 errors: [
4342 {
4343 message:
4344 "React Hook useEffect has a missing dependency: 'props'. " +
4345 'Either include it or remove the dependency array. ' +
4346 `However, 'props' will change when *any* prop changes, so the ` +
4347 `preferred fix is to destructure the 'props' object outside ` +
4348 `of the useEffect call and refer to those specific ` +
4349 `props inside useEffect.`,
4350 suggestions: [
4351 {
4352 desc: 'Update the dependencies array to be: [props]',
4353 output: normalizeIndent`
4354 function MyComponent(props) {
4355 useEffect(() => {
4356 if (props?.onChange) {
4357 props?.onChange();
4358 }
4359 }, [props]);
4360 }
4361 `,
4362 },
4363 ],
4364 },
4365 ],
4366 },
4367 {
4368 code: normalizeIndent`
4369 function MyComponent(props) {
4370 useEffect(() => {
4371 function play() {
4372 props.onPlay();
4373 }
4374 function pause() {
4375 props.onPause();
4376 }
4377 }, []);
4378 }
4379 `,
4380 errors: [
4381 {
4382 message:
4383 "React Hook useEffect has a missing dependency: 'props'. " +
4384 'Either include it or remove the dependency array. ' +
4385 `However, 'props' will change when *any* prop changes, so the ` +
4386 `preferred fix is to destructure the 'props' object outside ` +
4387 `of the useEffect call and refer to those specific ` +
4388 `props inside useEffect.`,
4389 suggestions: [
4390 {
4391 desc: 'Update the dependencies array to be: [props]',
4392 output: normalizeIndent`
4393 function MyComponent(props) {
4394 useEffect(() => {
4395 function play() {
4396 props.onPlay();
4397 }
4398 function pause() {
4399 props.onPause();
4400 }
4401 }, [props]);
4402 }
4403 `,
4404 },
4405 ],
4406 },
4407 ],
4408 },
4409 {
4410 code: normalizeIndent`
4411 function MyComponent(props) {
4412 useEffect(() => {
4413 if (props.foo.onChange) {
4414 props.foo.onChange();
4415 }
4416 }, []);
4417 }
4418 `,
4419 errors: [
4420 {
4421 message:
4422 "React Hook useEffect has a missing dependency: 'props.foo'. " +
4423 'Either include it or remove the dependency array.',
4424 suggestions: [
4425 {
4426 desc: 'Update the dependencies array to be: [props.foo]',
4427 output: normalizeIndent`
4428 function MyComponent(props) {
4429 useEffect(() => {
4430 if (props.foo.onChange) {
4431 props.foo.onChange();
4432 }
4433 }, [props.foo]);
4434 }
4435 `,
4436 },
4437 ],
4438 },
4439 ],
4440 },
4441 {
4442 code: normalizeIndent`
4443 function MyComponent(props) {
4444 useEffect(() => {
4445 props.onChange();
4446 if (props.foo.onChange) {
4447 props.foo.onChange();
4448 }
4449 }, []);
4450 }
4451 `,
4452 errors: [
4453 {
4454 message:
4455 "React Hook useEffect has a missing dependency: 'props'. " +
4456 'Either include it or remove the dependency array. ' +
4457 `However, 'props' will change when *any* prop changes, so the ` +
4458 `preferred fix is to destructure the 'props' object outside ` +
4459 `of the useEffect call and refer to those specific ` +
4460 `props inside useEffect.`,
4461 suggestions: [
4462 {
4463 desc: 'Update the dependencies array to be: [props]',
4464 output: normalizeIndent`
4465 function MyComponent(props) {
4466 useEffect(() => {
4467 props.onChange();
4468 if (props.foo.onChange) {
4469 props.foo.onChange();
4470 }
4471 }, [props]);
4472 }
4473 `,
4474 },
4475 ],
4476 },
4477 ],
4478 },
4479 {
4480 code: normalizeIndent`
4481 function MyComponent(props) {
4482 const [skillsCount] = useState();
4483 useEffect(() => {
4484 if (skillsCount === 0 && !props.isEditMode) {
4485 props.toggleEditMode();
4486 }
4487 }, [skillsCount, props.isEditMode, props.toggleEditMode]);
4488 }
4489 `,
4490 errors: [
4491 {
4492 message:
4493 "React Hook useEffect has a missing dependency: 'props'. " +
4494 'Either include it or remove the dependency array. ' +
4495 `However, 'props' will change when *any* prop changes, so the ` +
4496 `preferred fix is to destructure the 'props' object outside ` +
4497 `of the useEffect call and refer to those specific ` +
4498 `props inside useEffect.`,
4499 suggestions: [
4500 {
4501 desc: 'Update the dependencies array to be: [skillsCount, props.isEditMode, props.toggleEditMode, props]',
4502 output: normalizeIndent`
4503 function MyComponent(props) {
4504 const [skillsCount] = useState();
4505 useEffect(() => {
4506 if (skillsCount === 0 && !props.isEditMode) {
4507 props.toggleEditMode();
4508 }
4509 }, [skillsCount, props.isEditMode, props.toggleEditMode, props]);
4510 }
4511 `,
4512 },
4513 ],
4514 },
4515 ],
4516 },
4517 {
4518 code: normalizeIndent`
4519 function MyComponent(props) {
4520 const [skillsCount] = useState();
4521 useEffect(() => {
4522 if (skillsCount === 0 && !props.isEditMode) {
4523 props.toggleEditMode();
4524 }
4525 }, []);
4526 }
4527 `,
4528 errors: [
4529 {
4530 message:
4531 "React Hook useEffect has missing dependencies: 'props' and 'skillsCount'. " +
4532 'Either include them or remove the dependency array. ' +
4533 `However, 'props' will change when *any* prop changes, so the ` +
4534 `preferred fix is to destructure the 'props' object outside ` +
4535 `of the useEffect call and refer to those specific ` +
4536 `props inside useEffect.`,
4537 suggestions: [
4538 {
4539 desc: 'Update the dependencies array to be: [props, skillsCount]',
4540 output: normalizeIndent`
4541 function MyComponent(props) {
4542 const [skillsCount] = useState();
4543 useEffect(() => {
4544 if (skillsCount === 0 && !props.isEditMode) {
4545 props.toggleEditMode();
4546 }
4547 }, [props, skillsCount]);
4548 }
4549 `,
4550 },
4551 ],
4552 },
4553 ],
4554 },
4555 {
4556 code: normalizeIndent`
4557 function MyComponent(props) {
4558 useEffect(() => {
4559 externalCall(props);
4560 props.onChange();
4561 }, []);
4562 }
4563 `,
4564 // Don't suggest to destructure props here since you can't.
4565 errors: [
4566 {
4567 message:
4568 "React Hook useEffect has a missing dependency: 'props'. " +
4569 'Either include it or remove the dependency array.',
4570 suggestions: [
4571 {
4572 desc: 'Update the dependencies array to be: [props]',
4573 output: normalizeIndent`
4574 function MyComponent(props) {
4575 useEffect(() => {
4576 externalCall(props);
4577 props.onChange();
4578 }, [props]);
4579 }
4580 `,
4581 },
4582 ],
4583 },
4584 ],
4585 },
4586 {
4587 code: normalizeIndent`
4588 function MyComponent(props) {
4589 useEffect(() => {
4590 props.onChange();
4591 externalCall(props);
4592 }, []);
4593 }
4594 `,
4595 // Don't suggest to destructure props here since you can't.
4596 errors: [
4597 {
4598 message:
4599 "React Hook useEffect has a missing dependency: 'props'. " +
4600 'Either include it or remove the dependency array.',
4601 suggestions: [
4602 {
4603 desc: 'Update the dependencies array to be: [props]',
4604 output: normalizeIndent`
4605 function MyComponent(props) {
4606 useEffect(() => {
4607 props.onChange();
4608 externalCall(props);
4609 }, [props]);
4610 }
4611 `,
4612 },
4613 ],
4614 },
4615 ],
4616 },
4617 {
4618 code: normalizeIndent`
4619 function MyComponent(props) {
4620 let value;
4621 let value2;
4622 let value3;
4623 let value4;
4624 let asyncValue;
4625 useEffect(() => {
4626 if (value4) {
4627 value = {};
4628 }
4629 value2 = 100;
4630 value = 43;
4631 value4 = true;
4632 console.log(value2);
4633 console.log(value3);
4634 setTimeout(() => {
4635 asyncValue = 100;
4636 });
4637 }, []);
4638 }
4639 `,
4640 // This is a separate warning unrelated to others.
4641 // We could've made a separate rule for it but it's rare enough to name it.
4642 // No suggestions because the intent isn't clear.
4643 errors: [
4644 {
4645 message:
4646 // value2
4647 `Assignments to the 'value2' variable from inside React Hook useEffect ` +
4648 `will be lost after each render. To preserve the value over time, ` +
4649 `store it in a useRef Hook and keep the mutable value in the '.current' property. ` +
4650 `Otherwise, you can move this variable directly inside useEffect.`,
4651 suggestions: undefined,
4652 },
4653 {
4654 message:
4655 // value
4656 `Assignments to the 'value' variable from inside React Hook useEffect ` +
4657 `will be lost after each render. To preserve the value over time, ` +
4658 `store it in a useRef Hook and keep the mutable value in the '.current' property. ` +
4659 `Otherwise, you can move this variable directly inside useEffect.`,
4660 suggestions: undefined,
4661 },
4662 {
4663 message:
4664 // value4
4665 `Assignments to the 'value4' variable from inside React Hook useEffect ` +
4666 `will be lost after each render. To preserve the value over time, ` +
4667 `store it in a useRef Hook and keep the mutable value in the '.current' property. ` +
4668 `Otherwise, you can move this variable directly inside useEffect.`,
4669 suggestions: undefined,
4670 },
4671 {
4672 message:
4673 // asyncValue
4674 `Assignments to the 'asyncValue' variable from inside React Hook useEffect ` +
4675 `will be lost after each render. To preserve the value over time, ` +
4676 `store it in a useRef Hook and keep the mutable value in the '.current' property. ` +
4677 `Otherwise, you can move this variable directly inside useEffect.`,
4678 suggestions: undefined,
4679 },
4680 ],
4681 },
4682 {
4683 code: normalizeIndent`
4684 function MyComponent(props) {
4685 let value;
4686 let value2;
4687 let value3;
4688 let asyncValue;
4689 useEffect(() => {
4690 value = {};
4691 value2 = 100;
4692 value = 43;
4693 console.log(value2);
4694 console.log(value3);
4695 setTimeout(() => {
4696 asyncValue = 100;
4697 });
4698 }, [value, value2, value3]);
4699 }
4700 `,
4701 // This is a separate warning unrelated to others.
4702 // We could've made a separate rule for it but it's rare enough to name it.
4703 // No suggestions because the intent isn't clear.
4704 errors: [
4705 {
4706 message:
4707 // value
4708 `Assignments to the 'value' variable from inside React Hook useEffect ` +
4709 `will be lost after each render. To preserve the value over time, ` +
4710 `store it in a useRef Hook and keep the mutable value in the '.current' property. ` +
4711 `Otherwise, you can move this variable directly inside useEffect.`,
4712 suggestions: undefined,
4713 },
4714 {
4715 message:
4716 // value2
4717 `Assignments to the 'value2' variable from inside React Hook useEffect ` +
4718 `will be lost after each render. To preserve the value over time, ` +
4719 `store it in a useRef Hook and keep the mutable value in the '.current' property. ` +
4720 `Otherwise, you can move this variable directly inside useEffect.`,
4721 suggestions: undefined,
4722 },
4723 {
4724 message:
4725 // asyncValue
4726 `Assignments to the 'asyncValue' variable from inside React Hook useEffect ` +
4727 `will be lost after each render. To preserve the value over time, ` +
4728 `store it in a useRef Hook and keep the mutable value in the '.current' property. ` +
4729 `Otherwise, you can move this variable directly inside useEffect.`,
4730 suggestions: undefined,
4731 },
4732 ],
4733 },
4734 {
4735 code: normalizeIndent`
4736 function MyComponent() {
4737 const myRef = useRef();
4738 useEffect(() => {
4739 const handleMove = () => {};
4740 myRef.current.addEventListener('mousemove', handleMove);
4741 return () => myRef.current.removeEventListener('mousemove', handleMove);
4742 }, []);
4743 return <div ref={myRef} />;
4744 }
4745 `,
4746 errors: [
4747 {
4748 message:
4749 `The ref value 'myRef.current' will likely have changed by the time ` +
4750 `this effect cleanup function runs. If this ref points to a node ` +
4751 `rendered by React, copy 'myRef.current' to a variable inside the effect, ` +
4752 `and use that variable in the cleanup function.`,
4753 suggestions: undefined,
4754 },
4755 ],
4756 },
4757 {
4758 code: normalizeIndent`
4759 function MyComponent() {
4760 const myRef = useRef();
4761 useEffect(() => {
4762 const handleMove = () => {};
4763 myRef?.current?.addEventListener('mousemove', handleMove);
4764 return () => myRef?.current?.removeEventListener('mousemove', handleMove);
4765 }, []);
4766 return <div ref={myRef} />;
4767 }
4768 `,
4769 errors: [
4770 {
4771 message:
4772 `The ref value 'myRef.current' will likely have changed by the time ` +
4773 `this effect cleanup function runs. If this ref points to a node ` +
4774 `rendered by React, copy 'myRef.current' to a variable inside the effect, ` +
4775 `and use that variable in the cleanup function.`,
4776 suggestions: undefined,
4777 },
4778 ],
4779 },
4780 {
4781 code: normalizeIndent`
4782 function MyComponent() {
4783 const myRef = useRef();
4784 useEffect(() => {
4785 const handleMove = () => {};
4786 myRef.current.addEventListener('mousemove', handleMove);
4787 return () => myRef.current.removeEventListener('mousemove', handleMove);
4788 });
4789 return <div ref={myRef} />;
4790 }
4791 `,
4792 errors: [
4793 {
4794 message:
4795 `The ref value 'myRef.current' will likely have changed by the time ` +
4796 `this effect cleanup function runs. If this ref points to a node ` +
4797 `rendered by React, copy 'myRef.current' to a variable inside the effect, ` +
4798 `and use that variable in the cleanup function.`,
4799 suggestions: undefined,
4800 },
4801 ],
4802 },
4803 {
4804 code: normalizeIndent`
4805 function useMyThing(myRef) {
4806 useEffect(() => {
4807 const handleMove = () => {};
4808 myRef.current.addEventListener('mousemove', handleMove);
4809 return () => myRef.current.removeEventListener('mousemove', handleMove);
4810 }, [myRef]);
4811 }
4812 `,
4813 errors: [
4814 {
4815 message:
4816 `The ref value 'myRef.current' will likely have changed by the time ` +
4817 `this effect cleanup function runs. If this ref points to a node ` +
4818 `rendered by React, copy 'myRef.current' to a variable inside the effect, ` +
4819 `and use that variable in the cleanup function.`,
4820 suggestions: undefined,
4821 },
4822 ],
4823 },
4824 {
4825 code: normalizeIndent`
4826 function useMyThing(myRef) {
4827 useEffect(() => {
4828 const handleMouse = () => {};
4829 myRef.current.addEventListener('mousemove', handleMouse);
4830 myRef.current.addEventListener('mousein', handleMouse);
4831 return function() {
4832 setTimeout(() => {
4833 myRef.current.removeEventListener('mousemove', handleMouse);
4834 myRef.current.removeEventListener('mousein', handleMouse);
4835 });
4836 }
4837 }, [myRef]);
4838 }
4839 `,
4840 errors: [
4841 {
4842 message:
4843 `The ref value 'myRef.current' will likely have changed by the time ` +
4844 `this effect cleanup function runs. If this ref points to a node ` +
4845 `rendered by React, copy 'myRef.current' to a variable inside the effect, ` +
4846 `and use that variable in the cleanup function.`,
4847 suggestions: undefined,
4848 },
4849 ],
4850 },
4851 {
4852 code: normalizeIndent`
4853 function useMyThing(myRef, active) {
4854 useEffect(() => {
4855 const handleMove = () => {};
4856 if (active) {
4857 myRef.current.addEventListener('mousemove', handleMove);
4858 return function() {
4859 setTimeout(() => {
4860 myRef.current.removeEventListener('mousemove', handleMove);
4861 });
4862 }
4863 }
4864 }, [myRef, active]);
4865 }
4866 `,
4867 errors: [
4868 {
4869 message:
4870 `The ref value 'myRef.current' will likely have changed by the time ` +
4871 `this effect cleanup function runs. If this ref points to a node ` +
4872 `rendered by React, copy 'myRef.current' to a variable inside the effect, ` +
4873 `and use that variable in the cleanup function.`,
4874 suggestions: undefined,
4875 },
4876 ],
4877 },
4878 {
4879 code: `
4880 function MyComponent() {
4881 const myRef = useRef();
4882 useLayoutEffect_SAFE_FOR_SSR(() => {
4883 const handleMove = () => {};
4884 myRef.current.addEventListener('mousemove', handleMove);
4885 return () => myRef.current.removeEventListener('mousemove', handleMove);
4886 });
4887 return <div ref={myRef} />;
4888 }
4889 `,
4890 // No changes
4891 output: null,
4892 errors: [
4893 `The ref value 'myRef.current' will likely have changed by the time ` +
4894 `this effect cleanup function runs. If this ref points to a node ` +
4895 `rendered by React, copy 'myRef.current' to a variable inside the effect, ` +
4896 `and use that variable in the cleanup function.`,
4897 ],
4898 options: [{additionalHooks: 'useLayoutEffect_SAFE_FOR_SSR'}],
4899 },
4900 {
4901 // Autofix ignores constant primitives (leaving the ones that are there).
4902 code: normalizeIndent`
4903 function MyComponent() {
4904 const local1 = 42;
4905 const local2 = '42';
4906 const local3 = null;
4907 const local4 = {};
4908 useEffect(() => {
4909 console.log(local1);
4910 console.log(local2);
4911 console.log(local3);
4912 console.log(local4);
4913 }, [local1, local3]);
4914 }
4915 `,
4916 errors: [
4917 {
4918 message:
4919 "React Hook useEffect has a missing dependency: 'local4'. " +
4920 'Either include it or remove the dependency array.',
4921 suggestions: [
4922 {
4923 desc: 'Update the dependencies array to be: [local1, local3, local4]',
4924 output: normalizeIndent`
4925 function MyComponent() {
4926 const local1 = 42;
4927 const local2 = '42';
4928 const local3 = null;
4929 const local4 = {};
4930 useEffect(() => {
4931 console.log(local1);
4932 console.log(local2);
4933 console.log(local3);
4934 console.log(local4);
4935 }, [local1, local3, local4]);
4936 }
4937 `,
4938 },
4939 ],
4940 },
4941 ],
4942 },
4943 {
4944 code: normalizeIndent`
4945 function MyComponent() {
4946 useEffect(() => {
4947 window.scrollTo(0, 0);
4948 }, [window]);
4949 }
4950 `,
4951 errors: [
4952 {
4953 message:
4954 "React Hook useEffect has an unnecessary dependency: 'window'. " +
4955 'Either exclude it or remove the dependency array. ' +
4956 "Outer scope values like 'window' aren't valid dependencies " +
4957 "because mutating them doesn't re-render the component.",
4958 suggestions: [
4959 {
4960 desc: 'Update the dependencies array to be: []',
4961 output: normalizeIndent`
4962 function MyComponent() {
4963 useEffect(() => {
4964 window.scrollTo(0, 0);
4965 }, []);
4966 }
4967 `,
4968 },
4969 ],
4970 },
4971 ],
4972 },
4973 {
4974 code: normalizeIndent`
4975 import MutableStore from 'store';
4976
4977 function MyComponent() {
4978 useEffect(() => {
4979 console.log(MutableStore.hello);
4980 }, [MutableStore.hello]);
4981 }
4982 `,
4983 errors: [
4984 {
4985 message:
4986 "React Hook useEffect has an unnecessary dependency: 'MutableStore.hello'. " +
4987 'Either exclude it or remove the dependency array. ' +
4988 "Outer scope values like 'MutableStore.hello' aren't valid dependencies " +
4989 "because mutating them doesn't re-render the component.",
4990 suggestions: [
4991 {
4992 desc: 'Update the dependencies array to be: []',
4993 output: normalizeIndent`
4994 import MutableStore from 'store';
4995
4996 function MyComponent() {
4997 useEffect(() => {
4998 console.log(MutableStore.hello);
4999 }, []);
5000 }
Showing first 5,000 of 8,781 lines. View raw