packages/eslint-plugin-react-hooks/__tests__/ESLintRuleExhaustiveDeps-test.js JAVASCRIPT 8,782 lines View on github.com → Search inside
File is large — showing lines 1–2,000 of 8,782.
1/**2 * Copyright (c) Meta Platforms, Inc. and affiliates.3 *4 * This source code is licensed under the MIT license found in the5 * LICENSE file in the root directory of this source tree.6 *7 * @jest-environment node8 */910'use strict';1112const ESLintTesterV7 = require('eslint-v7').RuleTester;13const ESLintTesterV9 = require('eslint-v9').RuleTester;14const ReactHooksESLintPlugin = require('eslint-plugin-react-hooks');15const ReactHooksESLintRule =16  ReactHooksESLintPlugin.default.rules['exhaustive-deps'];1718/**19 * A string template tag that removes padding from the left side of multi-line strings20 * @param {Array} strings array of code strings (only one expected)21 */22function 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}2728// ***************************************************29// For easier local testing, you can add to any case:30// {31//   skip: true,32//   --or--33//   only: true,34//   ...35// }36// ***************************************************3738// Tests that are valid/invalid across all parsers39const 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 supposed74      // to be an import that hasn't been added yet, or75      // a component-level variable. Ignore it until it76      //  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 test159      code: normalizeIndent`160        function MyComponent({ foo }) {161          useEffect(() => {162            console.log(foo.length);163          }, [foo]);164        }165      `,166    },167    {168      // Regression test169      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 test180      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]);250251          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 chaining260    {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 anything394          };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 deps521      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(() => {}, []);635636          useEffect(() => {637            // Known to be static638            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();652653            // Dynamic654            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();664665            // Not sure; assume dynamic666            maybeSetState();667            maybeDispatch();668          }, [669            // Dynamic670            state1, state2, state3, state4, state5, state6, state9, state10,671            maybeRef1, maybeRef2,672            isPending2, isPending4,673674            // Not sure; assume dynamic675            mySetState, myDispatch,676            maybeSetState, maybeDispatch677678            // 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();690691          const [state1, setState1] = useState();692          const [state2, setState2] = React.useState();693          const [state3, dispatch1] = useReducer();694          const [state4, dispatch2] = React.useReducer();695696          const [state5, maybeSetState] = useFunnyState();697          const [state6, maybeDispatch] = useFunnyReducer();698699          const mySetState = useCallback(() => {}, []);700          let myDispatch = useCallback(() => {}, []);701702          useEffect(() => {703            // Known to be static704            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();712713            // Dynamic714            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();722723            // Not sure; assume dynamic724            maybeSetState();725            maybeDispatch();726          }, [727            // Dynamic728            state1, state2, state3, state4, state5, state6,729            maybeRef1, maybeRef2,730731            // Not sure; assume dynamic732            mySetState, myDispatch,733            maybeSetState, maybeDispatch,734735            // In this test, we specify static deps.736            // That should be okay too!737            definitelyRef1, definitelyRef2, setState1, setState2, dispatch1, dispatch2738          ]);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 create766      // too many false positives. We do, however, prevent767      // 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.current789      // ourselves. Therefore it's likely not790      // 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.current807      // ourselves. Therefore it's likely not808      // 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.current825      // ourselves. Therefore it's likely not826      // 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 because1010      // 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 because1036      // 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 because1056      // everything they use is fully static.1057      code: normalizeIndent`1058        function MyComponent(props) {1059          let [, setState] = useState();1060          let [, dispatch] = React.useReducer();10611062          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);11101111          useEffect(() => {1112            let id = setInterval(() => {1113              setCount(c => c + 1);1114            }, 1000);1115            return () => clearInterval(id);1116          }, []);11171118          return <h1>{count}</h1>;1119        }1120      `,1121    },1122    {1123      code: normalizeIndent`1124        function Counter(unstableProp) {1125          let [count, setCount] = useState(0);1126          setCount = unstableProp1127          useEffect(() => {1128            let id = setInterval(() => {1129              setCount(c => c + 1);1130            }, 1000);1131            return () => clearInterval(id);1132          }, [setCount]);11331134          return <h1>{count}</h1>;1135        }1136      `,1137    },1138    {1139      code: normalizeIndent`1140        function Counter() {1141          const [count, setCount] = useState(0);11421143          function tick() {1144            setCount(c => c + 1);1145          }11461147          useEffect(() => {1148            let id = setInterval(() => {1149              tick();1150            }, 1000);1151            return () => clearInterval(id);1152          }, []);11531154          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);11661167          useEffect(() => {1168            let id = setInterval(() => {1169              dispatch('inc');1170            }, 1000);1171            return () => clearInterval(id);1172          }, []);11731174          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);11861187          const tick = () => {1188            dispatch('inc');1189          };11901191          useEffect(() => {1192            let id = setInterval(tick, 1000);1193            return () => clearInterval(id);1194          }, []);11951196          return <h1>{count}</h1>;1197        }1198      `,1199    },1200    {1201      // Regression test for a crash1202      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);12401241          function increment(x) {1242            return x + 1;1243          }12441245          useEffect(() => {1246            let id = setInterval(() => {1247              setCount(increment);1248            }, 1000);1249            return () => clearInterval(id);1250          }, []);12511252          return <h1>{count}</h1>;1253        }1254      `,1255    },1256    {1257      code: normalizeIndent`1258        function Counter() {1259          let [count, setCount] = useState(0);12601261          function increment(x) {1262            return x + 1;1263          }12641265          useEffect(() => {1266            let id = setInterval(() => {1267              setCount(count => increment(count));1268            }, 1000);1269            return () => clearInterval(id);1270          }, []);12711272          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);12811282          useEffect(() => {1283            let id = setInterval(() => {1284              setCount(count => count + increment);1285            }, 1000);1286            return () => clearInterval(id);1287          }, []);12881289          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);12981299            useEffect(() => {1300              let id = setInterval(() => {1301                setCount(count => count + increment);1302              }, 1000);1303              return () => clearInterval(id);1304            }, []);13051306            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            arguments1372          }, [])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 settings1490      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 dependencies1505      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 settings1520      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 pattern1536      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 instead1631      code: normalizeIndent`1632        function ComponentUsingFormState(props) {1633          const [state7, dispatch3] = useFormState();1634          const [state8, dispatch4] = ReactDOM.useFormState();1635          useEffect(() => {1636            dispatch3();1637            dispatch4();16381639            // dynamic1640            console.log(state7);1641            console.log(state8);16421643          }, [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();16611662                    // dynamic1663                    console.log(state7);1664                    console.log(state8);16651666                  }, [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 = unstableProp1793          useEffect(() => {1794            let id = setInterval(() => {1795              setCount(c => c + 1);1796            }, 1000);1797            return () => clearInterval(id);1798          }, []);17991800          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 = unstableProp1815                  useEffect(() => {1816                    let id = setInterval(() => {1817                      setCount(c => c + 1);1818                    }, 1000);1819                    return () => clearInterval(id);1820                  }, [setCount]);18211822                  return <h1>{count}</h1>;1823                }1824              `,1825            },1826          ],1827        },1828      ],1829    },1830    {1831      // Note: we *could* detect it's a primitive and never assigned1832      // 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 test1979      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(() => {

Findings

✓ No findings reported for this file.

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.