@samitouri / QOS-React-2 / commits / 5ac9ca72df

Feature: Add support to `exhaustive-deps` rule for any hook ending with `Effect` (#18580)

* Add test cases for support exhaustive deps ending in Effect * Apply the exhaustive deps lint rule to any hook ending with Effect * Add another test for supporting linting useXEffect hooks Co-authored-by: Aaron Pettengill <aaron.pettengill@echoman.com>

Aaron Pettengill committed May 1, 2020 at 11:57 UTC 5ac9ca72dfb73a06157bb177cd695f6b77fc900e
2 files changed +156 -8
packages/eslint-plugin-react-hooks/__tests__/ESLintRuleExhaustiveDeps-test.js
+153 -7
@@ -360,32 +360,50 @@ const tests = {
360 {
361 code: normalizeIndent`
362 function MyComponent(props) {
363 - useCustomEffect(() => {
363 + useCustomHook(() => {
364 console.log(props.foo);
365 });
366 }
367 `,
368 - options: [{additionalHooks: 'useCustomEffect'}],
368 + options: [{additionalHooks: 'useCustomHook'}],
369 },
370 {
371 code: normalizeIndent`
372 function MyComponent(props) {
373 - useCustomEffect(() => {
373 + useCustomHook(() => {
374 console.log(props.foo);
375 }, [props.foo]);
376 }
377 `,
378 - options: [{additionalHooks: 'useCustomEffect'}],
378 + options: [{additionalHooks: 'useCustomHook'}],
379 },
380 {
381 code: normalizeIndent`
382 function MyComponent(props) {
383 - useCustomEffect(() => {
383 + useCustomHook(() => {
384 console.log(props.foo);
385 }, []);
386 }
387 `,
388 - options: [{additionalHooks: 'useAnotherEffect'}],
388 + options: [{additionalHooks: 'useAnotherHook'}],
389 + },
390 + {
391 + code: normalizeIndent`
392 + function MyComponent(props) {
393 + useCustomEffect(() => {
394 + console.log(props.foo);
395 + });
396 + }
397 + `,
398 + },
399 + {
400 + code: normalizeIndent`
401 + function MyComponent(props) {
402 + useCustomEffect(() => {
403 + console.log(props.foo);
404 + }, [props.foo]);
405 + }
406 + `,
407 },
408 {
409 // Valid because we don't care about hooks outside of components.
@@ -3002,6 +3020,105 @@ const tests = {
3020 },
3021 ],
3022 },
3023 + {
3024 + code: normalizeIndent`
3025 + function MyComponent(props) {
3026 + useCustomHook(() => {
3027 + console.log(props.foo);
3028 + }, []);
3029 + useEffect(() => {
3030 + console.log(props.foo);
3031 + }, []);
3032 + React.useEffect(() => {
3033 + console.log(props.foo);
3034 + }, []);
3035 + React.useCustomHook(() => {
3036 + console.log(props.foo);
3037 + }, []);
3038 + }
3039 + `,
3040 + options: [{additionalHooks: 'useCustomHook'}],
3041 + errors: [
3042 + {
3043 + message:
3044 + "React Hook useCustomHook has a missing dependency: 'props.foo'. " +
3045 + 'Either include it or remove the dependency array.',
3046 + suggestions: [
3047 + {
3048 + desc: 'Update the dependencies array to be: [props.foo]',
3049 + output: normalizeIndent`
3050 + function MyComponent(props) {
3051 + useCustomHook(() => {
3052 + console.log(props.foo);
3053 + }, [props.foo]);
3054 + useEffect(() => {
3055 + console.log(props.foo);
3056 + }, []);
3057 + React.useEffect(() => {
3058 + console.log(props.foo);
3059 + }, []);
3060 + React.useCustomHook(() => {
3061 + console.log(props.foo);
3062 + }, []);
3063 + }
3064 + `,
3065 + },
3066 + ],
3067 + },
3068 + {
3069 + message:
3070 + "React Hook useEffect has a missing dependency: 'props.foo'. " +
3071 + 'Either include it or remove the dependency array.',
3072 + suggestions: [
3073 + {
3074 + desc: 'Update the dependencies array to be: [props.foo]',
3075 + output: normalizeIndent`
3076 + function MyComponent(props) {
3077 + useCustomHook(() => {
3078 + console.log(props.foo);
3079 + }, []);
3080 + useEffect(() => {
3081 + console.log(props.foo);
3082 + }, [props.foo]);
3083 + React.useEffect(() => {
3084 + console.log(props.foo);
3085 + }, []);
3086 + React.useCustomHook(() => {
3087 + console.log(props.foo);
3088 + }, []);
3089 + }
3090 + `,
3091 + },
3092 + ],
3093 + },
3094 + {
3095 + message:
3096 + "React Hook React.useEffect has a missing dependency: 'props.foo'. " +
3097 + 'Either include it or remove the dependency array.',
3098 + suggestions: [
3099 + {
3100 + desc: 'Update the dependencies array to be: [props.foo]',
3101 + output: normalizeIndent`
3102 + function MyComponent(props) {
3103 + useCustomHook(() => {
3104 + console.log(props.foo);
3105 + }, []);
3106 + useEffect(() => {
3107 + console.log(props.foo);
3108 + }, []);
3109 + React.useEffect(() => {
3110 + console.log(props.foo);
3111 + }, [props.foo]);
3112 + React.useCustomHook(() => {
3113 + console.log(props.foo);
3114 + }, []);
3115 + }
3116 + `,
3117 + },
3118 + ],
3119 + },
3120 + ],
3121 + },
3122 {
3123 code: normalizeIndent`
3124 function MyComponent(props) {
@@ -3019,7 +3136,6 @@ const tests = {
3136 }, []);
3137 }
3138 `,
3022 - options: [{additionalHooks: 'useCustomEffect'}],
3139 errors: [
3140 {
3141 message:
@@ -4054,6 +4170,36 @@ const tests = {
4170 ],
4171 options: [{additionalHooks: 'useLayoutEffect_SAFE_FOR_SSR'}],
4172 },
4173 + {
4174 + code: `
4175 + function MyComponent() {
4176 + const myRef = useRef();
4177 + useIsomorphicLayoutEffect(() => {
4178 + const handleMove = () => {};
4179 + myRef.current.addEventListener('mousemove', handleMove);
4180 + return () => myRef.current.removeEventListener('mousemove', handleMove);
4181 + });
4182 + return <div ref={myRef} />;
4183 + }
4184 + `,
4185 + output: `
4186 + function MyComponent() {
4187 + const myRef = useRef();
4188 + useIsomorphicLayoutEffect(() => {
4189 + const handleMove = () => {};
4190 + myRef.current.addEventListener('mousemove', handleMove);
4191 + return () => myRef.current.removeEventListener('mousemove', handleMove);
4192 + });
4193 + return <div ref={myRef} />;
4194 + }
4195 + `,
4196 + errors: [
4197 + `The ref value 'myRef.current' will likely have changed by the time ` +
4198 + `this effect cleanup function runs. If this ref points to a node ` +
4199 + `rendered by React, copy 'myRef.current' to a variable inside the effect, ` +
4200 + `and use that variable in the cleanup function.`,
4201 + ],
4202 + },
4203 {
4204 // Autofix ignores constant primitives (leaving the ones that are there).
4205 code: normalizeIndent`
packages/eslint-plugin-react-hooks/src/ExhaustiveDeps.js
+3 -1
@@ -1504,7 +1504,9 @@ function getReactiveHookCallbackIndex(calleeNode, options) {
1504 // useImperativeHandle(ref, fn)
1505 return 1;
1506 default:
1507 - if (node === calleeNode && options && options.additionalHooks) {
1507 + if (node === calleeNode && node.name.match(/use.+Effect/)) {
1508 + return 0;
1509 + } else if (node === calleeNode && options && options.additionalHooks) {
1510 // Allow the user to provide a regular expression which enables the lint to
1511 // target custom reactive hooks.
1512 let name;