Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const ReactHooksESLintRule =

/**
* A string template tag that removes padding from the left side of multi-line strings
* @param {Array} strings array of code strings (only one expected)
* @param {TemplateStringsArray} strings array of code strings (only one expected)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this change is unrelated. It fixes the 400+ typescript errors in this file

*/
function normalizeIndent(strings) {
const codeLines = strings[0].split('\n');
Expand Down Expand Up @@ -7362,6 +7362,70 @@ const tests = {
},
],
},
{
code: normalizeIndent`
function Component({ foo = [] }) {
useMemo(() => foo, [foo]);
}
`,
errors: [
{
message:
"The 'foo' array makes the dependencies of useMemo Hook (at line 3) change on every render. " +
"Move it inside the useMemo callback. Alternatively, wrap the initialization of 'foo' in its own " +
'useMemo() Hook.',
suggestions: undefined,
},
],
},
{
code: normalizeIndent`
function useCustomHook(foo = []) {
useMemo(() => foo, [foo]);
}
`,
errors: [
{
message:
"The 'foo' array makes the dependencies of useMemo Hook (at line 3) change on every render. " +
"Move it inside the useMemo callback. Alternatively, wrap the initialization of 'foo' in its own " +
'useMemo() Hook.',
suggestions: undefined,
},
],
},
{
code: normalizeIndent`
function useCustomHook(bar, foo = {}) {
useMemo(() => foo, [foo]);
}
`,
errors: [
{
message:
"The 'foo' object makes the dependencies of useMemo Hook (at line 3) change on every render. " +
"Move it inside the useMemo callback. Alternatively, wrap the initialization of 'foo' in its own " +
'useMemo() Hook.',
suggestions: undefined,
},
],
},
{
code: normalizeIndent`
function Component({ foo = {} }) {
useMemo(() => foo, [foo]);
}
`,
errors: [
{
message:
"The 'foo' object makes the dependencies of useMemo Hook (at line 3) change on every render. " +
"Move it inside the useMemo callback. Alternatively, wrap the initialization of 'foo' in its own " +
'useMemo() Hook.',
suggestions: undefined,
},
],
},
{
code: normalizeIndent`
function Component() {
Expand Down
54 changes: 54 additions & 0 deletions packages/eslint-plugin-react-hooks/src/rules/ExhaustiveDeps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1759,6 +1759,43 @@ function getConstructionExpressionType(node: Node): string | null {
return null;
}

/**
* Function parameters can contain assignment (to set
* the default value).
* This function scans the parameters, and returns
* the identifer's default value, if it has one.
*/
function findIdentifierAssignmentInArguments(
params: Array<Pattern>,
identifierName: string,
): Expression | null {
for (const param of params) {
// an object argument like `function ({ x = …, y = … }) {}`
if (param.type === 'ObjectPattern') {
for (const prop of param.properties) {
if (
prop.type === 'Property' &&
prop.key.type === 'Identifier' &&
prop.key.name === identifierName &&
prop.value.type === 'AssignmentPattern'
) {
return prop.value.right;
}
}
}

// a normal argument like `function (x = …, y = …) {}`
if (
param.type === 'AssignmentPattern' &&
param.left.type === 'Identifier' &&
param.left.name === identifierName
) {
return param.right;
}
}
return null;
}

// Finds variables declared as dependencies
// that would invalidate on every render.
function scanForConstructions({
Expand Down Expand Up @@ -1813,6 +1850,23 @@ function scanForConstructions({
if (node.type === 'ClassName' && node.node.type === 'ClassDeclaration') {
return [ref, 'class'];
}

// function ({ x = [] }) {}
// or
// function (x = []) {}
if (node.type === 'Parameter') {
const value = findIdentifierAssignmentInArguments(
node.node.params,
key,
);
if (value) {
const constantExpressionType = getConstructionExpressionType(value);
if (constantExpressionType) {
return [ref, constantExpressionType];
}
}
}

return null;
})
.filter(Boolean) as Array<[Scope.Variable, string]>;
Expand Down