Skip to content

Commit

Permalink
[Fix] no-unstable-nested-components: Improve error message and catc…
Browse files Browse the repository at this point in the history
…h React.memo()

Fixes #3231
  • Loading branch information
zacharyliu authored and ljharb committed Mar 7, 2022
1 parent 316bc40 commit 1fdf9bd
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 8 deletions.
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.`;
}

/**
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 }],
},
]),
});

0 comments on commit 1fdf9bd

Please sign in to comment.