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;