Skip to content

Commit 1fdf9bd

Browse files
zacharyliuljharb
authored andcommitted
[Fix] no-unstable-nested-components: Improve error message and catch React.memo()
Fixes #3231
1 parent 316bc40 commit 1fdf9bd

File tree

3 files changed

+76
-8
lines changed

3 files changed

+76
-8
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
2020
* [`jsx-no-useless-fragment`]: use proper apostrophe in error message ([#3266][] @develohpanda)
2121
* `propTypes`: handle imported types/interface in forwardRef generic ([#3280][] @vedadeepta)
2222
* [`button-has-type`]: fix exception for `<button type>` ([#3255][] @meowtec)
23+
* [`no-unstable-nested-components`]: Improve error message and catch React.memo() ([#3247][] @zacharyliu)
2324

2425
### Changed
2526
* [readme] remove global usage and eslint version from readme ([#3254][] @aladdin-add)
@@ -55,6 +56,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
5556
[#3251]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3251
5657
[#3249]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3249
5758
[#3248]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3248
59+
[#3247]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3247
5860
[#3244]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3244
5961
[#3235]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3235
6062
[#3230]: https://github.com/jsx-eslint/eslint-plugin-react/issues/3230

lib/rules/no-unstable-nested-components.js

+4-6
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ const report = require('../util/report');
1414
// Constants
1515
// ------------------------------------------------------------------------------
1616

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

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

2524
/**
2625
* Generate error message with given parent component name
27-
* @param {String} parentName Name of the parent component
26+
* @param {String} parentName Name of the parent component, if known
2827
* @returns {String} Error message with parent component name
2928
*/
3029
function generateErrorMessageWithParentName(parentName) {
31-
return `Declare this component outside parent component "${parentName}" or memoize it.`;
30+
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.`;
3231
}
3332

3433
/**
@@ -464,9 +463,7 @@ module.exports = {
464463
return;
465464
}
466465

467-
let message = parentName
468-
? generateErrorMessageWithParentName(parentName)
469-
: ERROR_MESSAGE_WITHOUT_NAME;
466+
let message = generateErrorMessageWithParentName(parentName);
470467

471468
// Add information about allowAsProps option when component is declared inside prop
472469
if (isDeclaredInsideProps && !allowAsProps) {
@@ -488,6 +485,7 @@ module.exports = {
488485
ArrowFunctionExpression(node) { validate(node); },
489486
FunctionExpression(node) { validate(node); },
490487
ClassDeclaration(node) { validate(node); },
488+
CallExpression(node) { validate(node); },
491489
};
492490
}),
493491
};

tests/lib/rules/no-unstable-nested-components.js

+70-2
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ const parserOptions = {
2222
},
2323
};
2424

25-
const ERROR_MESSAGE = 'Declare this component outside parent component "ParentComponent" or memoize it.';
26-
const ERROR_MESSAGE_WITHOUT_NAME = 'Declare this component outside parent component or memoize it.';
25+
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.';
26+
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.';
2727
const ERROR_MESSAGE_COMPONENT_AS_PROPS = `${ERROR_MESSAGE} If you want to allow component creation in props, set allowAsProps option to true.`;
2828

2929
// ------------------------------------------------------------------------------
@@ -1175,5 +1175,73 @@ ruleTester.run('no-unstable-nested-components', rule, {
11751175
`,
11761176
errors: [{ message: ERROR_MESSAGE_COMPONENT_AS_PROPS }],
11771177
},
1178+
{
1179+
code: `
1180+
function ParentComponent() {
1181+
const UnstableNestedComponent = React.memo(() => {
1182+
return <div />;
1183+
});
1184+
1185+
return (
1186+
<div>
1187+
<UnstableNestedComponent />
1188+
</div>
1189+
);
1190+
}
1191+
`,
1192+
errors: [{ message: ERROR_MESSAGE }],
1193+
},
1194+
{
1195+
code: `
1196+
function ParentComponent() {
1197+
const UnstableNestedComponent = React.memo(
1198+
() => React.createElement("div", null),
1199+
);
1200+
1201+
return React.createElement(
1202+
"div",
1203+
null,
1204+
React.createElement(UnstableNestedComponent, null)
1205+
);
1206+
}
1207+
`,
1208+
errors: [{ message: ERROR_MESSAGE }],
1209+
},
1210+
{
1211+
code: `
1212+
function ParentComponent() {
1213+
const UnstableNestedComponent = React.memo(
1214+
function () {
1215+
return <div />;
1216+
}
1217+
);
1218+
1219+
return (
1220+
<div>
1221+
<UnstableNestedComponent />
1222+
</div>
1223+
);
1224+
}
1225+
`,
1226+
errors: [{ message: ERROR_MESSAGE }],
1227+
},
1228+
{
1229+
code: `
1230+
function ParentComponent() {
1231+
const UnstableNestedComponent = React.memo(
1232+
function () {
1233+
return React.createElement("div", null);
1234+
}
1235+
);
1236+
1237+
return React.createElement(
1238+
"div",
1239+
null,
1240+
React.createElement(UnstableNestedComponent, null)
1241+
);
1242+
}
1243+
`,
1244+
errors: [{ message: ERROR_MESSAGE }],
1245+
},
11781246
]),
11791247
});

0 commit comments

Comments
 (0)