diff --git a/CHANGELOG.md b/CHANGELOG.md index f5975115a2..75b94cc39d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,10 +14,12 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange ### Changed * [Docs] [`no-unknown-property`]: fix typo in link ([#3445][] @denkristoffer) * [Perf] component detection: improve performance by optimizing getId ([#3451][] @golopot) +* [Docs] [`no-unstable-nested-components`]: Warn about memoized, nested components ([#3444][] @eps1lon) [#3451]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3451 [#3448]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3448 [#3445]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3445 +[#3444]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3444 [#3436]: https://github.com/jsx-eslint/eslint-plugin-react/issues/3436 [#3337]: https://github.com/jsx-eslint/eslint-plugin-react/issues/3337 [#1591]: https://github.com/jsx-eslint/eslint-plugin-react/pull/1591 diff --git a/docs/rules/no-unstable-nested-components.md b/docs/rules/no-unstable-nested-components.md index 7e0abc93d4..01e280fd2f 100644 --- a/docs/rules/no-unstable-nested-components.md +++ b/docs/rules/no-unstable-nested-components.md @@ -2,9 +2,9 @@ 💼 This rule is enabled in the following [configs](https://github.com/jsx-eslint/eslint-plugin-react#shareable-configurations): `all`. -Creating components inside components without memoization leads to unstable components. The nested component and all its children are recreated during each re-render. Given stateful children of the nested component will lose their state on each re-render. +Creating components inside components (nested components) will cause React to throw away the state of those nested components on each re-render of their parent. -React reconciliation performs element type comparison with [reference equality](https://github.com/facebook/react/blob/v16.13.1/packages/react-reconciler/src/ReactChildFiber.js#L407). The reference to the same element changes on each re-render when defining components inside the render block. This leads to complete recreation of the current node and all its children. As a result the virtual DOM has to do extra unnecessary work and [possible bugs are introduced](https://codepen.io/ariperkkio/pen/vYLodLB). +React reconciliation performs element type comparison with [reference equality](https://reactjs.org/docs/reconciliation.html#elements-of-different-types). The reference to the same element changes on each re-render when defining components inside the render block. This leads to complete recreation of the current node and all its children. As a result the virtual DOM has to do extra unnecessary work and [possible bugs are introduced](https://codepen.io/ariperkkio/pen/vYLodLB). ## Rule Details @@ -76,6 +76,20 @@ function Component() { ```jsx function Component() { + return } />; +} +``` + +⚠️ WARNING ⚠️: + +Creating nested but memoized components is currently not detected by this rule but should also be avoided. +If the `useCallback` or `useMemo` hook has no dependency, you can safely move the component definition out of the render function. +If the hook does have dependencies, you should refactor the code so that you're able to move the component definition out of the render function. +If you want React to throw away the state of the nested component, use a [`key`](https://reactjs.org/docs/lists-and-keys.html#keys) instead. + +```jsx +function Component() { + // No ESLint warning but `MemoizedNestedComponent` should be moved outside of `Component`. const MemoizedNestedComponent = React.useCallback(() =>
, []); return ( @@ -86,14 +100,6 @@ function Component() { } ``` -```jsx -function Component() { - return ( - } /> - ) -} -``` - By default component creation is allowed inside component props only if prop name starts with `render`. See `allowAsProps` option for disabling this limitation completely. ```jsx diff --git a/tests/lib/rules/no-unstable-nested-components.js b/tests/lib/rules/no-unstable-nested-components.js index fd383fb8b5..a27444ebfd 100644 --- a/tests/lib/rules/no-unstable-nested-components.js +++ b/tests/lib/rules/no-unstable-nested-components.js @@ -78,6 +78,7 @@ ruleTester.run('no-unstable-nested-components', rule, { `, }, { + // false-negative. code: ` function ParentComponent() { const MemoizedNestedComponent = React.useCallback(() =>
, []); @@ -91,6 +92,7 @@ ruleTester.run('no-unstable-nested-components', rule, { `, }, { + // false-negative. code: ` function ParentComponent() { const MemoizedNestedComponent = React.useCallback( @@ -107,6 +109,7 @@ ruleTester.run('no-unstable-nested-components', rule, { `, }, { + // false-negative. code: ` function ParentComponent() { const MemoizedNestedFunctionComponent = React.useCallback( @@ -125,6 +128,7 @@ ruleTester.run('no-unstable-nested-components', rule, { `, }, { + // false-negative. code: ` function ParentComponent() { const MemoizedNestedFunctionComponent = React.useCallback(