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
Fred Vollmer committed
Jun 30, 2020 at 07:14 UTC
b0533fe33cbbd2c07f11bf3a1e97494a73698016
2 files changed
+52
-2
packages/eslint-plugin-react-hooks/__tests__/ESLintRuleExhaustiveDeps-test.js
+47
@@ -289,6 +289,24 @@ const tests = {
289
}
290
`,
291
},
292
+ {
293
+ code: normalizeIndent`
294
+ function MyComponent(props) {
295
+ useMemo(() => {
296
+ console.log(props.foo?.toString());
297
+ }, [props.foo]);
298
+ }
299
+ `,
300
+ },
301
+ {
302
+ code: normalizeIndent`
303
+ function MyComponent(props) {
304
+ useCallback(() => {
305
+ console.log(props.foo?.toString());
306
+ }, [props.foo]);
307
+ }
308
+ `,
309
+ },
310
{
311
code: normalizeIndent`
312
function MyComponent() {
@@ -1235,6 +1253,35 @@ const tests = {
1253
},
1254
],
1255
invalid: [
1256
+ {
1257
+ code: normalizeIndent`
1258
+ function MyComponent(props) {
1259
+ useCallback(() => {
1260
+ console.log(props.foo?.toString());
1261
+ }, []);
1262
+ }
1263
+ `,
1264
+ errors: [
1265
+ {
1266
+ message:
1267
+ "React Hook useCallback has a missing dependency: 'props.foo?.toString'. " +
1268
+ 'Either include it or remove the dependency array.',
1269
+ suggestions: [
1270
+ {
1271
+ desc:
1272
+ 'Update the dependencies array to be: [props.foo?.toString]',
1273
+ output: normalizeIndent`
1274
+ function MyComponent(props) {
1275
+ useCallback(() => {
1276
+ console.log(props.foo?.toString());
1277
+ }, [props.foo?.toString]);
1278
+ }
1279
+ `,
1280
+ },
1281
+ ],
1282
+ },
1283
+ ],
1284
+ },
1285
{
1286
code: normalizeIndent`
1287
function MyComponent() {
packages/eslint-plugin-react-hooks/src/ExhaustiveDeps.js
+5
-2
@@ -1287,10 +1287,13 @@ function collectRecommendations({
1287
function scanTreeRecursively(node, missingPaths, satisfyingPaths, keyToPath) {
1288
node.children.forEach((child, key) => {
1289
const path = keyToPath(key);
1290
+ // For analyzing dependencies, we want the "normalized" path, without any optional chaining ("?.") operator
1291
+ // foo?.bar -> foo.bar
1292
+ const normalizedPath = path.replace(/\?$/, '');
1293
if (child.isSatisfiedRecursively) {
1294
if (child.hasRequiredNodesBelow) {
1295
// Remember this dep actually satisfied something.
1293
- satisfyingPaths.add(path);
1296
+ satisfyingPaths.add(normalizedPath);
1297
}
1298
// It doesn't matter if there's something deeper.
1299
// It would be transitively satisfied since we assume immutability.
@@ -1299,7 +1302,7 @@ function collectRecommendations({
1302
}
1303
if (child.isRequired) {
1304
// Remember that no declared deps satisfied this node.
1302
- missingPaths.add(path);
1305
+ missingPaths.add(normalizedPath);
1306
// If we got here, nothing in its subtree was satisfied.
1307
// No need to search further.
1308
return;