Skip to content

Commit 7b35ee7

Browse files
AriPerkkioljharb
authored andcommitted
[New] no-unstable-nested-components: Prevent creating unstable components inside components
1 parent 4dda913 commit 7b35ee7

File tree

6 files changed

+1667
-1
lines changed

6 files changed

+1667
-1
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel
1010
* [`jsx-pascal-case`]: support minimatch `ignore` option ([#2906][] @bcherny)
1111
* [`jsx-pascal-case`]: support `allowNamespace` option ([#2917][] @kev-y-huang)
1212
* [`jsx-newline`]: Add prevent option ([#2935][] @jsphstls)
13+
* [`no-unstable-nested-components`]: Prevent creating unstable components inside components ([#2750][] @AriPerkkio)
1314

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

5153
## [7.22.0] - 2020.12.29
5254

@@ -3306,3 +3308,4 @@ If you're still not using React 15 you can keep the old behavior by setting the
33063308
[`function-component-definition`]: docs/rules/function-component-definition.md
33073309
[`jsx-newline`]: docs/rules/jsx-newline.md
33083310
[`jsx-no-constructed-context-values`]: docs/rules/jsx-no-constructed-context-values.md
3311+
[`no-unstable-nested-components`]: docs/rules/no-unstable-nested-components.md

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ Enable the rules that you would like to use.
137137
|| | [react/no-unescaped-entities](docs/rules/no-unescaped-entities.md) | Detect unescaped HTML entities, which might represent malformed tags |
138138
|| 🔧 | [react/no-unknown-property](docs/rules/no-unknown-property.md) | Prevent usage of unknown DOM property |
139139
| | | [react/no-unsafe](docs/rules/no-unsafe.md) | Prevent usage of unsafe lifecycle methods |
140+
| | | [react/no-unstable-nested-components](docs/rules/no-unstable-nested-components.md) | Prevent creating unstable components inside components |
140141
| | | [react/no-unused-prop-types](docs/rules/no-unused-prop-types.md) | Prevent definitions of unused prop types |
141142
| | | [react/no-unused-state](docs/rules/no-unused-state.md) | Prevent definition of unused state fields |
142143
| | | [react/no-will-update-set-state](docs/rules/no-will-update-set-state.md) | Prevent usage of setState in componentWillUpdate |
@@ -179,7 +180,7 @@ Enable the rules that you would like to use.
179180
|| | [react/jsx-key](docs/rules/jsx-key.md) | Report missing `key` props in iterators/collection literals |
180181
| | | [react/jsx-max-depth](docs/rules/jsx-max-depth.md) | Validate JSX maximum depth |
181182
| | 🔧 | [react/jsx-max-props-per-line](docs/rules/jsx-max-props-per-line.md) | Limit maximum of props on a single line in JSX |
182-
| | 🔧 | [react/jsx-newline](docs/rules/jsx-newline.md) | Require or prevent a new line after jsx elements and expressions |
183+
| | 🔧 | [react/jsx-newline](docs/rules/jsx-newline.md) | Require or prevent a new line after jsx elements and expressions. |
183184
| | | [react/jsx-no-bind](docs/rules/jsx-no-bind.md) | Prevents usage of Function.prototype.bind and arrow functions in React component props |
184185
|| | [react/jsx-no-comment-textnodes](docs/rules/jsx-no-comment-textnodes.md) | Comments inside children section of tag should be placed inside braces |
185186
| | | [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. |
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
# Prevent creating unstable components inside components (react/no-unstable-nested-components)
2+
3+
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.
4+
5+
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).
6+
7+
## Rule Details
8+
9+
The following patterns are considered warnings:
10+
11+
```jsx
12+
function Component() {
13+
function UnstableNestedComponent() {
14+
return <div />;
15+
}
16+
17+
return (
18+
<div>
19+
<UnstableNestedComponent />
20+
</div>
21+
);
22+
}
23+
```
24+
25+
```jsx
26+
function SomeComponent({ footer: Footer }) {
27+
return (
28+
<div>
29+
<Footer />
30+
</div>
31+
);
32+
}
33+
34+
function Component() {
35+
return (
36+
<div>
37+
<SomeComponent footer={() => <div />} />
38+
</div>
39+
);
40+
}
41+
```
42+
43+
```jsx
44+
class Component extends React.Component {
45+
render() {
46+
function UnstableNestedComponent() {
47+
return <div />;
48+
}
49+
50+
return (
51+
<div>
52+
<UnstableNestedComponent />
53+
</div>
54+
);
55+
}
56+
}
57+
```
58+
59+
The following patterns are **not** considered warnings:
60+
61+
```jsx
62+
function OutsideDefinedComponent(props) {
63+
return <div />;
64+
}
65+
66+
function Component() {
67+
return (
68+
<div>
69+
<OutsideDefinedComponent />
70+
</div>
71+
);
72+
}
73+
```
74+
75+
```jsx
76+
function Component() {
77+
const MemoizedNestedComponent = React.useCallback(() => <div />, []);
78+
79+
return (
80+
<div>
81+
<MemoizedNestedComponent />
82+
</div>
83+
);
84+
}
85+
```
86+
87+
```jsx
88+
function Component() {
89+
return (
90+
<SomeComponent footer={<div />} />
91+
)
92+
}
93+
```
94+
95+
By default component creation is allowed inside component props only if prop name starts with `render`. See `allowAsProps` option for disabling this limitation completely.
96+
97+
```jsx
98+
function SomeComponent(props) {
99+
return <div>{props.renderFooter()}</div>;
100+
}
101+
102+
function Component() {
103+
return (
104+
<div>
105+
<SomeComponent renderFooter={() => <div />} />
106+
</div>
107+
);
108+
}
109+
```
110+
111+
## Rule Options
112+
113+
```js
114+
...
115+
"react/no-unstable-nested-components": [
116+
"off" | "warn" | "error",
117+
{ "allowAsProps": true | false }
118+
]
119+
...
120+
```
121+
122+
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.
123+
124+
The following patterns are **not** considered warnings:
125+
126+
```jsx
127+
function SomeComponent(props) {
128+
return <div>{props.footer()}</div>;
129+
}
130+
131+
function Component() {
132+
return (
133+
<div>
134+
<SomeComponent footer={() => <div />} />
135+
</div>
136+
);
137+
}
138+
```
139+
140+
## When Not To Use It
141+
142+
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.

index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ const allRules = {
7676
'no-unescaped-entities': require('./lib/rules/no-unescaped-entities'),
7777
'no-unknown-property': require('./lib/rules/no-unknown-property'),
7878
'no-unsafe': require('./lib/rules/no-unsafe'),
79+
'no-unstable-nested-components': require('./lib/rules/no-unstable-nested-components'),
7980
'no-unused-prop-types': require('./lib/rules/no-unused-prop-types'),
8081
'no-unused-state': require('./lib/rules/no-unused-state'),
8182
'no-will-update-set-state': require('./lib/rules/no-will-update-set-state'),

0 commit comments

Comments
 (0)