@samitouri / QOS-React / commits / 1204c78977

[eslint] Wording tweaks (#15078)

* [eslint] Wording tweaks I think these are a little clearer. * fix tests

Sophie Alpert committed Mar 13, 2019 at 11:31 UTC 1204c789776cb01fbaf3e9f032e7e2ba85a44137
3 files changed +170 -156
fixtures/eslint/.eslintrc.json
+1 -1
@@ -1,7 +1,7 @@
1 {
2 "root": true,
3 "parserOptions": {
4 - "ecmaVersion": 6,
4 + "ecmaVersion": 8,
5 "sourceType": "module",
6 "ecmaFeatures": {
7 "jsx": true
packages/eslint-plugin-react-hooks/__tests__/ESLintRuleExhaustiveDeps-test.js
+122 -112
@@ -1060,12 +1060,10 @@ const tests = {
1060 }
1061 `,
1062 errors: [
1063 - "React Hook useMemo doesn't serve any purpose without a dependency array. " +
1064 - 'To enable this optimization, pass an array of values used by the inner ' +
1065 - 'function as the second argument to useMemo.',
1066 - "React Hook useCallback doesn't serve any purpose without a dependency array. " +
1067 - 'To enable this optimization, pass an array of values used by the inner ' +
1068 - 'function as the second argument to useCallback.',
1063 + 'React Hook useMemo does nothing when called with only one argument. ' +
1064 + 'Did you forget to pass an array of dependencies?',
1065 + 'React Hook useCallback does nothing when called with only one argument. ' +
1066 + 'Did you forget to pass an array of dependencies?',
1067 ],
1068 },
1069 {
@@ -1260,7 +1258,7 @@ const tests = {
1258 "React Hook useCallback has a missing dependency: 'local2'. " +
1259 'Either include it or remove the dependency array. ' +
1260 "Outer scope values like 'local1' aren't valid dependencies " +
1263 - "because their mutation doesn't re-render the component.",
1261 + "because mutating them doesn't re-render the component.",
1262 ],
1263 },
1264 {
@@ -1326,7 +1324,7 @@ const tests = {
1324 "React Hook useCallback has an unnecessary dependency: 'window'. " +
1325 'Either exclude it or remove the dependency array. ' +
1326 "Outer scope values like 'window' aren't valid dependencies " +
1329 - "because their mutation doesn't re-render the component.",
1327 + "because mutating them doesn't re-render the component.",
1328 ],
1329 },
1330 {
@@ -1395,6 +1393,24 @@ const tests = {
1393 'Either include it or remove the dependency array.',
1394 ],
1395 },
1396 + {
1397 + code: `
1398 + function MyComponent() {
1399 + useEffect(() => {}, ['foo']);
1400 + }
1401 + `,
1402 + // TODO: we could autofix this.
1403 + output: `
1404 + function MyComponent() {
1405 + useEffect(() => {}, ['foo']);
1406 + }
1407 + `,
1408 + errors: [
1409 + // Don't assume user meant `foo` because it's not used in the effect.
1410 + "The 'foo' literal is not a valid dependency because it never changes. " +
1411 + 'You can safely remove it.',
1412 + ],
1413 + },
1414 {
1415 code: `
1416 function MyComponent({ foo, bar, baz }) {
@@ -1413,9 +1429,9 @@ const tests = {
1429 errors: [
1430 "React Hook useEffect has missing dependencies: 'bar', 'baz', and 'foo'. " +
1431 'Either include them or remove the dependency array.',
1416 - "The 'foo' string literal is not a valid dependency because it never changes. " +
1432 + "The 'foo' literal is not a valid dependency because it never changes. " +
1433 'Did you mean to include foo in the array instead?',
1418 - "The 'bar' string literal is not a valid dependency because it never changes. " +
1434 + "The 'bar' literal is not a valid dependency because it never changes. " +
1435 'Did you mean to include bar in the array instead?',
1436 ],
1437 },
@@ -1437,9 +1453,9 @@ const tests = {
1453 errors: [
1454 "React Hook useEffect has missing dependencies: 'bar', 'baz', and 'foo'. " +
1455 'Either include them or remove the dependency array.',
1440 - "The '42' literal is not a valid dependency because it never changes. You can safely remove it.",
1441 - "The 'false' literal is not a valid dependency because it never changes. You can safely remove it.",
1442 - "The 'null' literal is not a valid dependency because it never changes. You can safely remove it.",
1456 + 'The 42 literal is not a valid dependency because it never changes. You can safely remove it.',
1457 + 'The false literal is not a valid dependency because it never changes. You can safely remove it.',
1458 + 'The null literal is not a valid dependency because it never changes. You can safely remove it.',
1459 ],
1460 },
1461 {
@@ -1456,8 +1472,8 @@ const tests = {
1472 }
1473 `,
1474 errors: [
1459 - 'React Hook useEffect has a second argument which is not an array ' +
1460 - "literal. This means we can't statically verify whether you've " +
1475 + 'React Hook useEffect was passed a dependency list that is not an ' +
1476 + "array literal. This means we can't statically verify whether you've " +
1477 'passed the correct dependencies.',
1478 ],
1479 },
@@ -1482,8 +1498,8 @@ const tests = {
1498 }
1499 `,
1500 errors: [
1485 - 'React Hook useEffect has a second argument which is not an array ' +
1486 - "literal. This means we can't statically verify whether you've " +
1501 + 'React Hook useEffect was passed a dependency list that is not an ' +
1502 + "array literal. This means we can't statically verify whether you've " +
1503 'passed the correct dependencies.',
1504 "React Hook useEffect has a missing dependency: 'local'. " +
1505 'Either include it or remove the dependency array.',
@@ -2327,8 +2343,8 @@ const tests = {
2343 errors: [
2344 "React Hook useEffect has a missing dependency: 'state'. " +
2345 'Either include it or remove the dependency array. ' +
2330 - `You can also write 'setState(s => ...)' ` +
2331 - `if you only use 'state' for the 'setState' call.`,
2346 + `You can also do a functional update 'setState(s => ...)' ` +
2347 + `if you only need 'state' in the 'setState' call.`,
2348 ],
2349 },
2350 {
@@ -2358,8 +2374,8 @@ const tests = {
2374 errors: [
2375 "React Hook useEffect has a missing dependency: 'state'. " +
2376 'Either include it or remove the dependency array. ' +
2361 - `You can also write 'setState(s => ...)' ` +
2362 - `if you only use 'state' for the 'setState' call.`,
2377 + `You can also do a functional update 'setState(s => ...)' ` +
2378 + `if you only need 'state' in the 'setState' call.`,
2379 ],
2380 },
2381 {
@@ -2421,7 +2437,7 @@ const tests = {
2437 "React Hook useEffect has unnecessary dependencies: 'ref1.current' and 'ref2.current'. " +
2438 'Either exclude them or remove the dependency array. ' +
2439 "Mutable values like 'ref1.current' aren't valid dependencies " +
2424 - "because their mutation doesn't re-render the component.",
2440 + "because mutating them doesn't re-render the component.",
2441 ],
2442 },
2443 {
@@ -2445,7 +2461,7 @@ const tests = {
2461 "React Hook useEffect has an unnecessary dependency: 'ref.current'. " +
2462 'Either exclude it or remove the dependency array. ' +
2463 "Mutable values like 'ref.current' aren't valid dependencies " +
2448 - "because their mutation doesn't re-render the component.",
2464 + "because mutating them doesn't re-render the component.",
2465 ],
2466 },
2467 {
@@ -2473,7 +2489,7 @@ const tests = {
2489 "React Hook useEffect has unnecessary dependencies: 'ref1.current' and 'ref2.current'. " +
2490 'Either exclude them or remove the dependency array. ' +
2491 "Mutable values like 'ref1.current' aren't valid dependencies " +
2476 - "because their mutation doesn't re-render the component.",
2492 + "because mutating them doesn't re-render the component.",
2493 ],
2494 },
2495 {
@@ -2501,7 +2517,7 @@ const tests = {
2517 "React Hook useCallback has unnecessary dependencies: 'activeTab', 'ref1.current', and 'ref2.current'. " +
2518 'Either exclude them or remove the dependency array. ' +
2519 "Mutable values like 'ref1.current' aren't valid dependencies " +
2504 - "because their mutation doesn't re-render the component.",
2520 + "because mutating them doesn't re-render the component.",
2521 ],
2522 },
2523 {
@@ -2525,7 +2541,7 @@ const tests = {
2541 "React Hook useEffect has an unnecessary dependency: 'ref.current'. " +
2542 'Either exclude it or remove the dependency array. ' +
2543 "Mutable values like 'ref.current' aren't valid dependencies " +
2528 - "because their mutation doesn't re-render the component.",
2544 + "because mutating them doesn't re-render the component.",
2545 ],
2546 },
2547 {
@@ -2574,9 +2590,10 @@ const tests = {
2590 errors: [
2591 "React Hook useEffect has a missing dependency: 'props'. " +
2592 'Either include it or remove the dependency array. ' +
2577 - `However, the preferred fix is to destructure the 'props' ` +
2578 - `object outside of the useEffect call and refer to specific ` +
2579 - `props directly by their names.`,
2593 + `However, 'props' will change when *any* prop changes, so the ` +
2594 + `preferred fix is to destructure the 'props' object outside ` +
2595 + `of the useEffect call and refer to those specific ` +
2596 + `props inside useEffect.`,
2597 ],
2598 },
2599 {
@@ -2607,9 +2624,10 @@ const tests = {
2624 errors: [
2625 "React Hook useEffect has a missing dependency: 'props'. " +
2626 'Either include it or remove the dependency array. ' +
2610 - `However, the preferred fix is to destructure the 'props' ` +
2611 - `object outside of the useEffect call and refer to specific ` +
2612 - `props directly by their names.`,
2627 + `However, 'props' will change when *any* prop changes, so the ` +
2628 + `preferred fix is to destructure the 'props' object outside ` +
2629 + `of the useEffect call and refer to those specific ` +
2630 + `props inside useEffect.`,
2631 ],
2632 },
2633 {
@@ -2660,9 +2678,10 @@ const tests = {
2678 errors: [
2679 "React Hook useEffect has a missing dependency: 'props'. " +
2680 'Either include it or remove the dependency array. ' +
2663 - `However, the preferred fix is to destructure the 'props' ` +
2664 - `object outside of the useEffect call and refer to specific ` +
2665 - `props directly by their names.`,
2681 + `However, 'props' will change when *any* prop changes, so the ` +
2682 + `preferred fix is to destructure the 'props' object outside ` +
2683 + `of the useEffect call and refer to those specific ` +
2684 + `props inside useEffect.`,
2685 ],
2686 },
2687 {
@@ -2689,9 +2708,10 @@ const tests = {
2708 errors: [
2709 "React Hook useEffect has a missing dependency: 'props'. " +
2710 'Either include it or remove the dependency array. ' +
2692 - `However, the preferred fix is to destructure the 'props' ` +
2693 - `object outside of the useEffect call and refer to specific ` +
2694 - `props directly by their names.`,
2711 + `However, 'props' will change when *any* prop changes, so the ` +
2712 + `preferred fix is to destructure the 'props' object outside ` +
2713 + `of the useEffect call and refer to those specific ` +
2714 + `props inside useEffect.`,
2715 ],
2716 },
2717 {
@@ -2718,9 +2738,10 @@ const tests = {
2738 errors: [
2739 "React Hook useEffect has missing dependencies: 'props' and 'skillsCount'. " +
2740 'Either include them or remove the dependency array. ' +
2721 - `However, the preferred fix is to destructure the 'props' ` +
2722 - `object outside of the useEffect call and refer to specific ` +
2723 - `props directly by their names.`,
2741 + `However, 'props' will change when *any* prop changes, so the ` +
2742 + `preferred fix is to destructure the 'props' object outside ` +
2743 + `of the useEffect call and refer to those specific ` +
2744 + `props inside useEffect.`,
2745 ],
2746 },
2747 {
@@ -2819,29 +2840,25 @@ const tests = {
2840 `,
2841 errors: [
2842 // value2
2822 - `Assignments to the 'value2' variable from inside a React useEffect Hook ` +
2823 - `will not persist between re-renders. ` +
2824 - `If it's only needed by this Hook, move the variable inside it. ` +
2825 - `Alternatively, declare a ref with the useRef Hook, ` +
2826 - `and keep the mutable value in its 'current' property.`,
2843 + `Assignments to the 'value2' variable from inside React Hook useEffect ` +
2844 + `will be lost after each render. To preserve the value over time, ` +
2845 + `store it in a useRef Hook and keep the mutable value in the '.current' property. ` +
2846 + `Otherwise, you can move this variable directly inside useEffect.`,
2847 // value
2828 - `Assignments to the 'value' variable from inside a React useEffect Hook ` +
2829 - `will not persist between re-renders. ` +
2830 - `If it's only needed by this Hook, move the variable inside it. ` +
2831 - `Alternatively, declare a ref with the useRef Hook, ` +
2832 - `and keep the mutable value in its 'current' property.`,
2848 + `Assignments to the 'value' variable from inside React Hook useEffect ` +
2849 + `will be lost after each render. To preserve the value over time, ` +
2850 + `store it in a useRef Hook and keep the mutable value in the '.current' property. ` +
2851 + `Otherwise, you can move this variable directly inside useEffect.`,
2852 // value4
2834 - `Assignments to the 'value4' variable from inside a React useEffect Hook ` +
2835 - `will not persist between re-renders. ` +
2836 - `If it's only needed by this Hook, move the variable inside it. ` +
2837 - `Alternatively, declare a ref with the useRef Hook, ` +
2838 - `and keep the mutable value in its 'current' property.`,
2853 + `Assignments to the 'value4' variable from inside React Hook useEffect ` +
2854 + `will be lost after each render. To preserve the value over time, ` +
2855 + `store it in a useRef Hook and keep the mutable value in the '.current' property. ` +
2856 + `Otherwise, you can move this variable directly inside useEffect.`,
2857 // asyncValue
2840 - `Assignments to the 'asyncValue' variable from inside a React useEffect Hook ` +
2841 - `will not persist between re-renders. ` +
2842 - `If it's only needed by this Hook, move the variable inside it. ` +
2843 - `Alternatively, declare a ref with the useRef Hook, ` +
2844 - `and keep the mutable value in its 'current' property.`,
2858 + `Assignments to the 'asyncValue' variable from inside React Hook useEffect ` +
2859 + `will be lost after each render. To preserve the value over time, ` +
2860 + `store it in a useRef Hook and keep the mutable value in the '.current' property. ` +
2861 + `Otherwise, you can move this variable directly inside useEffect.`,
2862 ],
2863 },
2864 {
@@ -2886,23 +2903,20 @@ const tests = {
2903 `,
2904 errors: [
2905 // value
2889 - `Assignments to the 'value' variable from inside a React useEffect Hook ` +
2890 - `will not persist between re-renders. ` +
2891 - `If it's only needed by this Hook, move the variable inside it. ` +
2892 - `Alternatively, declare a ref with the useRef Hook, ` +
2893 - `and keep the mutable value in its 'current' property.`,
2906 + `Assignments to the 'value' variable from inside React Hook useEffect ` +
2907 + `will be lost after each render. To preserve the value over time, ` +
2908 + `store it in a useRef Hook and keep the mutable value in the '.current' property. ` +
2909 + `Otherwise, you can move this variable directly inside useEffect.`,
2910 // value2
2895 - `Assignments to the 'value2' variable from inside a React useEffect Hook ` +
2896 - `will not persist between re-renders. ` +
2897 - `If it's only needed by this Hook, move the variable inside it. ` +
2898 - `Alternatively, declare a ref with the useRef Hook, ` +
2899 - `and keep the mutable value in its 'current' property.`,
2911 + `Assignments to the 'value2' variable from inside React Hook useEffect ` +
2912 + `will be lost after each render. To preserve the value over time, ` +
2913 + `store it in a useRef Hook and keep the mutable value in the '.current' property. ` +
2914 + `Otherwise, you can move this variable directly inside useEffect.`,
2915 // asyncValue
2901 - `Assignments to the 'asyncValue' variable from inside a React useEffect Hook ` +
2902 - `will not persist between re-renders. ` +
2903 - `If it's only needed by this Hook, move the variable inside it. ` +
2904 - `Alternatively, declare a ref with the useRef Hook, ` +
2905 - `and keep the mutable value in its 'current' property.`,
2916 + `Assignments to the 'asyncValue' variable from inside React Hook useEffect ` +
2917 + `will be lost after each render. To preserve the value over time, ` +
2918 + `store it in a useRef Hook and keep the mutable value in the '.current' property. ` +
2919 + `Otherwise, you can move this variable directly inside useEffect.`,
2920 ],
2921 },
2922 {
@@ -2929,11 +2943,10 @@ const tests = {
2943 }
2944 `,
2945 errors: [
2932 - `Accessing 'myRef.current' during the effect cleanup ` +
2933 - `will likely read a different ref value because by this time React ` +
2934 - `has already updated the ref. If this ref is managed by React, store ` +
2935 - `'myRef.current' in a variable inside ` +
2936 - `the effect itself and refer to that variable from the cleanup function.`,
2946 + `The ref value 'myRef.current' will likely have changed by the time ` +
2947 + `this effect cleanup function runs. If this ref points to a node ` +
2948 + `rendered by React, copy 'myRef.current' to a variable inside the effect, ` +
2949 + `and use that variable in the cleanup function.`,
2950 ],
2951 },
2952 {
@@ -2956,11 +2969,10 @@ const tests = {
2969 }
2970 `,
2971 errors: [
2959 - `Accessing 'myRef.current' during the effect cleanup ` +
2960 - `will likely read a different ref value because by this time React ` +
2961 - `has already updated the ref. If this ref is managed by React, store ` +
2962 - `'myRef.current' in a variable inside ` +
2963 - `the effect itself and refer to that variable from the cleanup function.`,
2972 + `The ref value 'myRef.current' will likely have changed by the time ` +
2973 + `this effect cleanup function runs. If this ref points to a node ` +
2974 + `rendered by React, copy 'myRef.current' to a variable inside the effect, ` +
2975 + `and use that variable in the cleanup function.`,
2976 ],
2977 },
2978 {
@@ -2995,11 +3007,10 @@ const tests = {
3007 }
3008 `,
3009 errors: [
2998 - `Accessing 'myRef.current' during the effect cleanup ` +
2999 - `will likely read a different ref value because by this time React ` +
3000 - `has already updated the ref. If this ref is managed by React, store ` +
3001 - `'myRef.current' in a variable inside ` +
3002 - `the effect itself and refer to that variable from the cleanup function.`,
3010 + `The ref value 'myRef.current' will likely have changed by the time ` +
3011 + `this effect cleanup function runs. If this ref points to a node ` +
3012 + `rendered by React, copy 'myRef.current' to a variable inside the effect, ` +
3013 + `and use that variable in the cleanup function.`,
3014 ],
3015 },
3016 {
@@ -3034,11 +3045,10 @@ const tests = {
3045 }
3046 `,
3047 errors: [
3037 - `Accessing 'myRef.current' during the effect cleanup ` +
3038 - `will likely read a different ref value because by this time React ` +
3039 - `has already updated the ref. If this ref is managed by React, store ` +
3040 - `'myRef.current' in a variable inside ` +
3041 - `the effect itself and refer to that variable from the cleanup function.`,
3048 + `The ref value 'myRef.current' will likely have changed by the time ` +
3049 + `this effect cleanup function runs. If this ref points to a node ` +
3050 + `rendered by React, copy 'myRef.current' to a variable inside the effect, ` +
3051 + `and use that variable in the cleanup function.`,
3052 ],
3053 },
3054 {
@@ -3088,7 +3098,7 @@ const tests = {
3098 "React Hook useEffect has an unnecessary dependency: 'window'. " +
3099 'Either exclude it or remove the dependency array. ' +
3100 "Outer scope values like 'window' aren't valid dependencies " +
3091 - "because their mutation doesn't re-render the component.",
3101 + "because mutating them doesn't re-render the component.",
3102 ],
3103 },
3104 {
@@ -3114,7 +3124,7 @@ const tests = {
3124 "React Hook useEffect has an unnecessary dependency: 'MutableStore.hello'. " +
3125 'Either exclude it or remove the dependency array. ' +
3126 "Outer scope values like 'MutableStore.hello' aren't valid dependencies " +
3117 - "because their mutation doesn't re-render the component.",
3127 + "because mutating them doesn't re-render the component.",
3128 ],
3129 },
3130 {
@@ -3151,7 +3161,7 @@ const tests = {
3161 "'MutableStore.hello.world', 'global.stuff', and 'z'. " +
3162 'Either exclude them or remove the dependency array. ' +
3163 "Outer scope values like 'MutableStore.hello.world' aren't valid dependencies " +
3154 - "because their mutation doesn't re-render the component.",
3164 + "because mutating them doesn't re-render the component.",
3165 ],
3166 },
3167 {
@@ -3190,7 +3200,7 @@ const tests = {
3200 "'MutableStore.hello.world', 'global.stuff', and 'z'. " +
3201 'Either exclude them or remove the dependency array. ' +
3202 "Outer scope values like 'MutableStore.hello.world' aren't valid dependencies " +
3193 - "because their mutation doesn't re-render the component.",
3203 + "because mutating them doesn't re-render the component.",
3204 ],
3205 },
3206 {
@@ -3227,7 +3237,7 @@ const tests = {
3237 "'MutableStore.hello.world', 'global.stuff', 'props.foo', 'x', 'y', and 'z'. " +
3238 'Either exclude them or remove the dependency array. ' +
3239 "Outer scope values like 'MutableStore.hello.world' aren't valid dependencies " +
3230 - "because their mutation doesn't re-render the component.",
3240 + "because mutating them doesn't re-render the component.",
3241 ],
3242 },
3243 {
@@ -3956,8 +3966,8 @@ const tests = {
3966 errors: [
3967 "React Hook useEffect has a missing dependency: 'count'. " +
3968 'Either include it or remove the dependency array. ' +
3959 - `You can also write 'setCount(c => ...)' if you ` +
3960 - `only use 'count' for the 'setCount' call.`,
3969 + `You can also do a functional update 'setCount(c => ...)' if you ` +
3970 + `only need 'count' in the 'setCount' call.`,
3971 ],
3972 },
3973 {
@@ -3994,8 +4004,8 @@ const tests = {
4004 errors: [
4005 "React Hook useEffect has missing dependencies: 'count' and 'increment'. " +
4006 'Either include them or remove the dependency array. ' +
3997 - `You can also write 'setCount(c => ...)' if you ` +
3998 - `only use 'count' for the 'setCount' call.`,
4007 + `You can also do a functional update 'setCount(c => ...)' if you ` +
4008 + `only need 'count' in the 'setCount' call.`,
4009 ],
4010 },
4011 {
@@ -4196,8 +4206,8 @@ const tests = {
4206 errors: [
4207 "React Hook useEffect has a missing dependency: 'increment'. " +
4208 'Either include it or remove the dependency array. ' +
4199 - 'You can also replace useState with an inline useReducer ' +
4200 - `if 'setCount' needs the current value of 'increment'.`,
4209 + `If 'setCount' needs the current value of 'increment', ` +
4210 + `you can also switch to useReducer instead of useState and read 'increment' in the reducer.`,
4211 ],
4212 },
4213 {
@@ -4292,7 +4302,7 @@ const tests = {
4302 errors: [
4303 `React Hook useEffect has a missing dependency: 'fetchPodcasts'. ` +
4304 `Either include it or remove the dependency array. ` +
4295 - `If specifying 'fetchPodcasts' makes the dependencies change too often, ` +
4305 + `If 'fetchPodcasts' changes too often, ` +
4306 `find the parent component that defines it and wrap that definition in useCallback.`,
4307 ],
4308 },
@@ -4316,7 +4326,7 @@ const tests = {
4326 errors: [
4327 `React Hook useEffect has a missing dependency: 'fetchPodcasts'. ` +
4328 `Either include it or remove the dependency array. ` +
4319 - `If specifying 'fetchPodcasts' makes the dependencies change too often, ` +
4329 + `If 'fetchPodcasts' changes too often, ` +
4330 `find the parent component that defines it and wrap that definition in useCallback.`,
4331 ],
4332 },
@@ -4348,7 +4358,7 @@ const tests = {
4358 errors: [
4359 `React Hook useEffect has missing dependencies: 'fetchPodcasts' and 'fetchPodcasts2'. ` +
4360 `Either include them or remove the dependency array. ` +
4351 - `If specifying 'fetchPodcasts' makes the dependencies change too often, ` +
4361 + `If 'fetchPodcasts' changes too often, ` +
4362 `find the parent component that defines it and wrap that definition in useCallback.`,
4363 ],
4364 },
@@ -4374,7 +4384,7 @@ const tests = {
4384 errors: [
4385 `React Hook useEffect has a missing dependency: 'fetchPodcasts'. ` +
4386 `Either include it or remove the dependency array. ` +
4377 - `If specifying 'fetchPodcasts' makes the dependencies change too often, ` +
4387 + `If 'fetchPodcasts' changes too often, ` +
4388 `find the parent component that defines it and wrap that definition in useCallback.`,
4389 ],
4390 },
@@ -4426,7 +4436,7 @@ const tests = {
4436 ` }\n` +
4437 `\n` +
4438 ` return () => { ignore = true; };\n` +
4429 - `}, []);\n` +
4439 + `}, ...);\n` +
4440 `\n` +
4441 `This lets you handle multiple requests without bugs.`,
4442 ],
packages/eslint-plugin-react-hooks/src/ExhaustiveDeps.js
+47 -43
@@ -94,13 +94,13 @@ export default {
94 reactiveHookName === 'useMemo' ||
95 reactiveHookName === 'useCallback'
96 ) {
97 + // TODO: Can this have an autofix?
98 context.report({
99 node: node.parent.callee,
100 message:
100 - `React Hook ${reactiveHookName} doesn't serve any purpose ` +
101 - `without a dependency array. To enable ` +
102 - `this optimization, pass an array of values used by the ` +
103 - `inner function as the second argument to ${reactiveHookName}.`,
101 + `React Hook ${reactiveHookName} does nothing when called with ` +
102 + `only one argument. Did you forget to pass an array of ` +
103 + `dependencies?`,
104 });
105 }
106 return;
@@ -122,7 +122,7 @@ export default {
122 ` }\n` +
123 `\n` +
124 ` return () => { ignore = true; };\n` +
125 - `}, []);\n` +
125 + `}, ...);\n` +
126 `\n` +
127 `This lets you handle multiple requests without bugs.`,
128 });
@@ -453,11 +453,11 @@ export default {
453 context.report({
454 node: dependencyNode.parent.property,
455 message:
456 - `Accessing '${dependency}.current' during the effect cleanup ` +
457 - `will likely read a different ref value because by this time React ` +
458 - `has already updated the ref. If this ref is managed by React, store ` +
459 - `'${dependency}.current' in a variable inside ` +
460 - `the effect itself and refer to that variable from the cleanup function.`,
456 + `The ref value '${dependency}.current' will likely have ` +
457 + `changed by the time this effect cleanup function runs. If ` +
458 + `this ref points to a node rendered by React, copy ` +
459 + `'${dependency}.current' to a variable inside the effect, and ` +
460 + `use that variable in the cleanup function.`,
461 });
462 },
463 );
@@ -471,9 +471,10 @@ export default {
471 context.report({
472 node: declaredDependenciesNode,
473 message:
474 - `React Hook ${context.getSource(reactiveHook)} has a second ` +
475 - "argument which is not an array literal. This means we can't " +
476 - "statically verify whether you've passed the correct dependencies.",
474 + `React Hook ${context.getSource(reactiveHook)} was passed a ` +
475 + 'dependency list that is not an array literal. This means we ' +
476 + "can't statically verify whether you've passed the correct " +
477 + 'dependencies.',
478 });
479 } else {
480 declaredDependenciesNode.elements.forEach(declaredDependencyNode => {
@@ -501,15 +502,15 @@ export default {
502 } catch (error) {
503 if (/Unsupported node type/.test(error.message)) {
504 if (declaredDependencyNode.type === 'Literal') {
504 - if (typeof declaredDependencyNode.value === 'string') {
505 + if (dependencies.has(declaredDependencyNode.value)) {
506 context.report({
507 node: declaredDependencyNode,
508 message:
509 `The ${
510 declaredDependencyNode.raw
510 - } string literal is not a valid dependency ` +
511 - `because it never changes. Did you mean to ` +
512 - `include ${
511 + } literal is not a valid dependency ` +
512 + `because it never changes. ` +
513 + `Did you mean to include ${
514 declaredDependencyNode.value
515 } in the array instead?`,
516 });
@@ -517,9 +518,9 @@ export default {
518 context.report({
519 node: declaredDependencyNode,
520 message:
520 - `The '${
521 + `The ${
522 declaredDependencyNode.raw
522 - }' literal is not a valid dependency ` +
523 + } literal is not a valid dependency ` +
524 'because it never changes. You can safely remove it.',
525 });
526 }
@@ -570,13 +571,12 @@ export default {
571 context.report({
572 node: writeExpr,
573 message:
573 - `Assignments to the '${key}' variable from inside a React ${context.getSource(
574 - reactiveHook,
575 - )} Hook ` +
576 - `will not persist between re-renders. ` +
577 - `If it's only needed by this Hook, move the variable inside it. ` +
578 - `Alternatively, declare a ref with the useRef Hook, ` +
579 - `and keep the mutable value in its 'current' property.`,
574 + `Assignments to the '${key}' variable from inside React Hook ` +
575 + `${context.getSource(reactiveHook)} will be lost after each ` +
576 + `render. To preserve the value over time, store it in a useRef ` +
577 + `Hook and keep the mutable value in the '.current' property. ` +
578 + `Otherwise, you can move this variable directly inside ` +
579 + `${context.getSource(reactiveHook)}.`,
580 });
581 }
582
@@ -644,7 +644,10 @@ export default {
644 fn.name.name
645 }' definition into its own useCallback() Hook.`;
646 }
647 + // TODO: What if the function needs to change on every render anyway?
648 + // Should we suggest removing effect deps as an appropriate fix too?
649 context.report({
650 + // TODO: Why not report this at the dependency site?
651 node: fn.node,
652 message,
653 fix(fixer) {
@@ -654,10 +657,10 @@ export default {
657 return [
658 // TODO: also add an import?
659 fixer.insertTextBefore(fn.node.init, 'useCallback('),
657 - // TODO: ideally we'd gather deps here but it would
658 - // require restructuring the rule code. For now,
659 - // this is fine. Note we're intentionally not adding
660 - // [] because that changes semantics.
660 + // TODO: ideally we'd gather deps here but it would require
661 + // restructuring the rule code. This will cause a new lint
662 + // error to appear immediately for useCallback. Note we're
663 + // not adding [] because would that changes semantics.
664 fixer.insertTextAfter(fn.node.init, ')'),
665 ];
666 }
@@ -731,7 +734,7 @@ export default {
734 if (badRef !== null) {
735 extraWarning =
736 ` Mutable values like '${badRef}' aren't valid dependencies ` +
734 - "because their mutation doesn't re-render the component.";
737 + "because mutating them doesn't re-render the component.";
738 } else if (externalDependencies.size > 0) {
739 const dep = Array.from(externalDependencies)[0];
740 // Don't show this warning for things that likely just got moved *inside* the callback
@@ -739,7 +742,7 @@ export default {
742 if (!scope.set.has(dep)) {
743 extraWarning =
744 ` Outer scope values like '${dep}' aren't valid dependencies ` +
742 - `because their mutation doesn't re-render the component.`;
745 + `because mutating them doesn't re-render the component.`;
746 }
747 }
748 }
@@ -779,9 +782,10 @@ export default {
782 }
783 if (isPropsOnlyUsedInMembers) {
784 extraWarning =
782 - ` However, the preferred fix is to destructure the 'props' ` +
783 - `object outside of the ${reactiveHookName} call and ` +
784 - `refer to specific props directly by their names.`;
785 + ` However, 'props' will change when *any* prop changes, so the ` +
786 + `preferred fix is to destructure the 'props' object outside of ` +
787 + `the ${reactiveHookName} call and refer to those specific props ` +
788 + `inside ${context.getSource(reactiveHook)}.`;
789 }
790 }
791
@@ -829,8 +833,7 @@ export default {
833 });
834 if (missingCallbackDep !== null) {
835 extraWarning =
832 - ` If specifying '${missingCallbackDep}'` +
833 - ` makes the dependencies change too often, ` +
836 + ` If '${missingCallbackDep}' changes too often, ` +
837 `find the parent component that defines it ` +
838 `and wrap that definition in useCallback.`;
839 }
@@ -906,20 +909,21 @@ export default {
909 break;
910 case 'inlineReducer':
911 extraWarning =
909 - ` You can also replace useState with an inline useReducer ` +
910 - `if '${setStateRecommendation.setter}' needs the ` +
911 - `current value of '${setStateRecommendation.missingDep}'.`;
912 + ` If '${setStateRecommendation.setter}' needs the ` +
913 + `current value of '${setStateRecommendation.missingDep}', ` +
914 + `you can also switch to useReducer instead of useState and ` +
915 + `read '${setStateRecommendation.missingDep}' in the reducer.`;
916 break;
917 case 'updater':
918 extraWarning =
915 - ` You can also write '${
919 + ` You can also do a functional update '${
920 setStateRecommendation.setter
921 }(${setStateRecommendation.missingDep.substring(
922 0,
923 1,
920 - )} => ...)' if you only use '${
924 + )} => ...)' if you only need '${
925 setStateRecommendation.missingDep
922 - }'` + ` for the '${setStateRecommendation.setter}' call.`;
926 + }'` + ` in the '${setStateRecommendation.setter}' call.`;
927 break;
928 default:
929 throw new Error('Unknown case.');