Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve no-unstable-nested-components error message and catch React.memo() #3247

Merged
merged 1 commit into from May 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -20,6 +20,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
* [`jsx-no-useless-fragment`]: use proper apostrophe in error message ([#3266][] @develohpanda)
* `propTypes`: handle imported types/interface in forwardRef generic ([#3280][] @vedadeepta)
* [`button-has-type`]: fix exception for `<button type>` ([#3255][] @meowtec)
* [`no-unstable-nested-components`]: Improve error message and catch React.memo() ([#3247][] @zacharyliu)

### Changed
* [readme] remove global usage and eslint version from readme ([#3254][] @aladdin-add)
Expand Down Expand Up @@ -55,6 +56,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
[#3251]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3251
[#3249]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3249
[#3248]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3248
[#3247]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3247
[#3244]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3244
[#3235]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3235
[#3230]: https://github.com/jsx-eslint/eslint-plugin-react/issues/3230
Expand Down
10 changes: 4 additions & 6 deletions lib/rules/no-unstable-nested-components.js
Expand Up @@ -14,7 +14,6 @@ const report = require('../util/report');
// Constants
// ------------------------------------------------------------------------------

const ERROR_MESSAGE_WITHOUT_NAME = 'Declare this component outside parent component or memoize it.';
const COMPONENT_AS_PROPS_INFO = ' If you want to allow component creation in props, set allowAsProps option to true.';
const HOOK_REGEXP = /^use[A-Z0-9].*$/;

Expand All @@ -24,11 +23,11 @@ const HOOK_REGEXP = /^use[A-Z0-9].*$/;

/**
* Generate error message with given parent component name
* @param {String} parentName Name of the parent component
* @param {String} parentName Name of the parent component, if known
* @returns {String} Error message with parent component name
*/
function generateErrorMessageWithParentName(parentName) {
return `Declare this component outside parent component "${parentName}" or memoize it.`;
return `Do not define components during render. React will see a new component type on every render and destroy the entire subtree’s DOM nodes and state (https://reactjs.org/docs/reconciliation.html#elements-of-different-types). Instead, move this component definition out of the parent component${parentName ? ` “${parentName}” ` : ' '}and pass data as props.`;
ljharb marked this conversation as resolved.
Show resolved Hide resolved
}

/**
Expand Down Expand Up @@ -464,9 +463,7 @@ module.exports = {
return;
}

let message = parentName
? generateErrorMessageWithParentName(parentName)
: ERROR_MESSAGE_WITHOUT_NAME;
let message = generateErrorMessageWithParentName(parentName);

// Add information about allowAsProps option when component is declared inside prop
if (isDeclaredInsideProps && !allowAsProps) {
Expand All @@ -488,6 +485,7 @@ module.exports = {
ArrowFunctionExpression(node) { validate(node); },
FunctionExpression(node) { validate(node); },
ClassDeclaration(node) { validate(node); },
CallExpression(node) { validate(node); },
};
}),
};
72 changes: 70 additions & 2 deletions tests/lib/rules/no-unstable-nested-components.js
Expand Up @@ -22,8 +22,8 @@ const parserOptions = {
},
};

const ERROR_MESSAGE = 'Declare this component outside parent component "ParentComponent" or memoize it.';
const ERROR_MESSAGE_WITHOUT_NAME = 'Declare this component outside parent component or memoize it.';
const ERROR_MESSAGE = 'Do not define components during render. React will see a new component type on every render and destroy the entire subtree’s DOM nodes and state (https://reactjs.org/docs/reconciliation.html#elements-of-different-types). Instead, move this component definition out of the parent component ParentComponent” and pass data as props.';
const ERROR_MESSAGE_WITHOUT_NAME = 'Do not define components during render. React will see a new component type on every render and destroy the entire subtree’s DOM nodes and state (https://reactjs.org/docs/reconciliation.html#elements-of-different-types). Instead, move this component definition out of the parent component and pass data as props.';
const ERROR_MESSAGE_COMPONENT_AS_PROPS = `${ERROR_MESSAGE} If you want to allow component creation in props, set allowAsProps option to true.`;

// ------------------------------------------------------------------------------
Expand Down Expand Up @@ -1175,5 +1175,73 @@ ruleTester.run('no-unstable-nested-components', rule, {
`,
errors: [{ message: ERROR_MESSAGE_COMPONENT_AS_PROPS }],
},
{
code: `
function ParentComponent() {
const UnstableNestedComponent = React.memo(() => {
return <div />;
});

return (
<div>
<UnstableNestedComponent />
</div>
);
}
`,
errors: [{ message: ERROR_MESSAGE }],
},
{
code: `
function ParentComponent() {
const UnstableNestedComponent = React.memo(
() => React.createElement("div", null),
);

return React.createElement(
"div",
null,
React.createElement(UnstableNestedComponent, null)
);
}
`,
errors: [{ message: ERROR_MESSAGE }],
},
{
code: `
function ParentComponent() {
const UnstableNestedComponent = React.memo(
function () {
return <div />;
}
);

return (
<div>
<UnstableNestedComponent />
</div>
);
}
`,
errors: [{ message: ERROR_MESSAGE }],
},
{
code: `
function ParentComponent() {
const UnstableNestedComponent = React.memo(
function () {
return React.createElement("div", null);
}
);

return React.createElement(
"div",
null,
React.createElement(UnstableNestedComponent, null)
);
}
`,
errors: [{ message: ERROR_MESSAGE }],
},
]),
});