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

[New] no-unstable-nested-components: Prevent creating unstable components inside components #2750

Merged
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Expand Up @@ -10,6 +10,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel
* [`jsx-pascal-case`]: support minimatch `ignore` option ([#2906][] @bcherny)
* [`jsx-pascal-case`]: support `allowNamespace` option ([#2917][] @kev-y-huang)
* [`jsx-newline`]: Add prevent option ([#2935][] @jsphstls)
* [`no-unstable-nested-components`]: Prevent creating unstable components inside components ([#2750][] @AriPerkkio)

### Fixed
* [`jsx-no-constructed-context-values`]: avoid a crash with `as X` TS code ([#2894][] @ljharb)
Expand Down Expand Up @@ -47,6 +48,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel
[#2894]: https://github.com/yannickcr/eslint-plugin-react/issues/2894
[#2893]: https://github.com/yannickcr/eslint-plugin-react/pull/2893
[#2862]: https://github.com/yannickcr/eslint-plugin-react/pull/2862
[#2750]: https://github.com/yannickcr/eslint-plugin-react/pull/2750

## [7.22.0] - 2020.12.29

Expand Down Expand Up @@ -3306,3 +3308,4 @@ If you're still not using React 15 you can keep the old behavior by setting the
[`function-component-definition`]: docs/rules/function-component-definition.md
[`jsx-newline`]: docs/rules/jsx-newline.md
[`jsx-no-constructed-context-values`]: docs/rules/jsx-no-constructed-context-values.md
[`no-unstable-nested-components`]: docs/rules/no-unstable-nested-components.md
3 changes: 2 additions & 1 deletion README.md
Expand Up @@ -137,6 +137,7 @@ Enable the rules that you would like to use.
| ✔ | | [react/no-unescaped-entities](docs/rules/no-unescaped-entities.md) | Detect unescaped HTML entities, which might represent malformed tags |
| ✔ | 🔧 | [react/no-unknown-property](docs/rules/no-unknown-property.md) | Prevent usage of unknown DOM property |
| | | [react/no-unsafe](docs/rules/no-unsafe.md) | Prevent usage of unsafe lifecycle methods |
| | | [react/no-unstable-nested-components](docs/rules/no-unstable-nested-components.md) | Prevent creating unstable components inside components |
| | | [react/no-unused-prop-types](docs/rules/no-unused-prop-types.md) | Prevent definitions of unused prop types |
| | | [react/no-unused-state](docs/rules/no-unused-state.md) | Prevent definition of unused state fields |
| | | [react/no-will-update-set-state](docs/rules/no-will-update-set-state.md) | Prevent usage of setState in componentWillUpdate |
Expand Down Expand Up @@ -179,7 +180,7 @@ Enable the rules that you would like to use.
| ✔ | | [react/jsx-key](docs/rules/jsx-key.md) | Report missing `key` props in iterators/collection literals |
| | | [react/jsx-max-depth](docs/rules/jsx-max-depth.md) | Validate JSX maximum depth |
| | 🔧 | [react/jsx-max-props-per-line](docs/rules/jsx-max-props-per-line.md) | Limit maximum of props on a single line in JSX |
| | 🔧 | [react/jsx-newline](docs/rules/jsx-newline.md) | Require or prevent a new line after jsx elements and expressions |
| | 🔧 | [react/jsx-newline](docs/rules/jsx-newline.md) | Require or prevent a new line after jsx elements and expressions. |
| | | [react/jsx-no-bind](docs/rules/jsx-no-bind.md) | Prevents usage of Function.prototype.bind and arrow functions in React component props |
| ✔ | | [react/jsx-no-comment-textnodes](docs/rules/jsx-no-comment-textnodes.md) | Comments inside children section of tag should be placed inside braces |
| | | [react/jsx-no-constructed-context-values](docs/rules/jsx-no-constructed-context-values.md) | Prevents JSX context provider values from taking values that will cause needless rerenders. |
Expand Down
142 changes: 142 additions & 0 deletions docs/rules/no-unstable-nested-components.md
@@ -0,0 +1,142 @@
# Prevent creating unstable components inside components (react/no-unstable-nested-components)

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.

React reconcilation 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).

## Rule Details

The following patterns are considered warnings:

```jsx
function Component() {
function UnstableNestedComponent() {
return <div />;
}

return (
<div>
<UnstableNestedComponent />
</div>
);
}
```

```jsx
function SomeComponent({ footer: Footer }) {
return (
<div>
<Footer />
</div>
);
}

function Component() {
return (
<div>
<SomeComponent footer={() => <div />} />
</div>
);
}
```

```jsx
class Component extends React.Component {
render() {
function UnstableNestedComponent() {
return <div />;
}

return (
<div>
<UnstableNestedComponent />
</div>
);
}
}
```

The following patterns are **not** considered warnings:

```jsx
function OutsideDefinedComponent(props) {
return <div />;
}

function Component() {
return (
<div>
<OutsideDefinedComponent />
</div>
);
}
```

```jsx
function Component() {
const MemoizedNestedComponent = React.useCallback(() => <div />, []);

return (
<div>
<MemoizedNestedComponent />
</div>
);
}
```

```jsx
function Component() {
return (
<SomeComponent footer={<div />} />
)
}
```

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
function SomeComponent(props) {
return <div>{props.renderFooter()}</div>;
}

function Component() {
return (
<div>
<SomeComponent renderFooter={() => <div />} />
</div>
);
}
```

## Rule Options

```js
...
"react/no-unstable-nested-components": [
"off" | "warn" | "error",
{ "allowAsProps": true | false }
]
...
```

You can allow component creation inside component props by setting `allowAsProps` option to true. When using this option make sure you are **calling** the props in the receiving component and not using them as elements.

The following patterns are **not** considered warnings:

```jsx
function SomeComponent(props) {
return <div>{props.footer()}</div>;
}

function Component() {
return (
<div>
<SomeComponent footer={() => <div />} />
</div>
);
}
```

## When Not To Use It

If you are not interested in preventing bugs related to re-creation of the nested components or do not care about optimization of virtual DOM.
1 change: 1 addition & 0 deletions index.js
Expand Up @@ -76,6 +76,7 @@ const allRules = {
'no-unescaped-entities': require('./lib/rules/no-unescaped-entities'),
'no-unknown-property': require('./lib/rules/no-unknown-property'),
'no-unsafe': require('./lib/rules/no-unsafe'),
'no-unstable-nested-components': require('./lib/rules/no-unstable-nested-components'),
'no-unused-prop-types': require('./lib/rules/no-unused-prop-types'),
'no-unused-state': require('./lib/rules/no-unused-state'),
'no-will-update-set-state': require('./lib/rules/no-will-update-set-state'),
Expand Down