From b0533fe33cbbd2c07f11bf3a1e97494a73698016 Mon Sep 17 00:00:00 2001 From: Fred Vollmer Date: Tue, 30 Jun 2020 07:14:00 -0400 Subject: [PATCH] fix(eslint-plugin-react-hooks): Support optional chaining when accessing prototype method inside useCallback and useMemo #19061 (#19062) * fix(eslint-plugin-react-hooks): Support optional chaining when accessing prototype method #19061 * run prettier * Add fix for #19043 --- .../ESLintRuleExhaustiveDeps-test.js | 47 +++++++++++++++++++ .../src/ExhaustiveDeps.js | 7 ++- 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/packages/eslint-plugin-react-hooks/__tests__/ESLintRuleExhaustiveDeps-test.js b/packages/eslint-plugin-react-hooks/__tests__/ESLintRuleExhaustiveDeps-test.js index 9322952fa4..68a3bd6b77 100644 --- a/packages/eslint-plugin-react-hooks/__tests__/ESLintRuleExhaustiveDeps-test.js +++ b/packages/eslint-plugin-react-hooks/__tests__/ESLintRuleExhaustiveDeps-test.js @@ -289,6 +289,24 @@ const tests = { } `, }, + { + code: normalizeIndent` + function MyComponent(props) { + useMemo(() => { + console.log(props.foo?.toString()); + }, [props.foo]); + } + `, + }, + { + code: normalizeIndent` + function MyComponent(props) { + useCallback(() => { + console.log(props.foo?.toString()); + }, [props.foo]); + } + `, + }, { code: normalizeIndent` function MyComponent() { @@ -1235,6 +1253,35 @@ const tests = { }, ], invalid: [ + { + code: normalizeIndent` + function MyComponent(props) { + useCallback(() => { + console.log(props.foo?.toString()); + }, []); + } + `, + errors: [ + { + message: + "React Hook useCallback has a missing dependency: 'props.foo?.toString'. " + + 'Either include it or remove the dependency array.', + suggestions: [ + { + desc: + 'Update the dependencies array to be: [props.foo?.toString]', + output: normalizeIndent` + function MyComponent(props) { + useCallback(() => { + console.log(props.foo?.toString()); + }, [props.foo?.toString]); + } + `, + }, + ], + }, + ], + }, { code: normalizeIndent` function MyComponent() { diff --git a/packages/eslint-plugin-react-hooks/src/ExhaustiveDeps.js b/packages/eslint-plugin-react-hooks/src/ExhaustiveDeps.js index 33ff43a663..8a23c88cec 100644 --- a/packages/eslint-plugin-react-hooks/src/ExhaustiveDeps.js +++ b/packages/eslint-plugin-react-hooks/src/ExhaustiveDeps.js @@ -1287,10 +1287,13 @@ function collectRecommendations({ function scanTreeRecursively(node, missingPaths, satisfyingPaths, keyToPath) { node.children.forEach((child, key) => { const path = keyToPath(key); + // For analyzing dependencies, we want the "normalized" path, without any optional chaining ("?.") operator + // foo?.bar -> foo.bar + const normalizedPath = path.replace(/\?$/, ''); if (child.isSatisfiedRecursively) { if (child.hasRequiredNodesBelow) { // Remember this dep actually satisfied something. - satisfyingPaths.add(path); + satisfyingPaths.add(normalizedPath); } // It doesn't matter if there's something deeper. // It would be transitively satisfied since we assume immutability. @@ -1299,7 +1302,7 @@ function collectRecommendations({ } if (child.isRequired) { // Remember that no declared deps satisfied this node. - missingPaths.add(path); + missingPaths.add(normalizedPath); // If we got here, nothing in its subtree was satisfied. // No need to search further. return;