Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[New]
no-unstable-nested-components
: Prevent creating unstable comp…
…onents inside components
- Loading branch information
1 parent
9abc71d
commit 0e24d1c
Showing
5 changed files
with
525 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
# 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 | ||
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> | ||
); | ||
} | ||
``` | ||
|
||
## Rule Options | ||
|
||
```js | ||
... | ||
"react/no-unstable-nested-components": "off" | "warn" | "error" | ||
... | ||
``` | ||
|
||
## 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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
/** | ||
* @fileoverview Prevent creating unstable components inside components | ||
* @author Ari Perkkiö | ||
*/ | ||
|
||
'use strict'; | ||
|
||
const Components = require('../util/Components'); | ||
const docsUrl = require('../util/docsUrl'); | ||
|
||
// ------------------------------------------------------------------------------ | ||
// Constants | ||
// ------------------------------------------------------------------------------ | ||
|
||
const ERROR_MESSAGE = 'Do not create unstable nested components. Declare them outside the component or memoize them.'; | ||
|
||
// ------------------------------------------------------------------------------ | ||
// Rule Definition | ||
// ------------------------------------------------------------------------------ | ||
|
||
module.exports = { | ||
meta: { | ||
docs: { | ||
description: 'Prevent creating unstable components inside components', | ||
category: 'Possible Errors', | ||
recommended: false, | ||
url: docsUrl('no-unstable-nested-components') | ||
}, | ||
schema: [] | ||
}, | ||
|
||
create: Components.detect((context, components, utils) => { | ||
/** | ||
* Check whether given node is declared inside another component | ||
* @param {ASTNode} node The AST node being checked | ||
* @returns {Boolean} True if node has a parent component, false if not | ||
*/ | ||
function isAnyParentComponent(node) { | ||
if (!node.parent || node.parent.type === 'Program') { | ||
return false; | ||
} | ||
|
||
return components.get(node.parent) || isAnyParentComponent(node.parent); | ||
} | ||
|
||
/** | ||
* Check whether given node is inside class component's render block | ||
* @param {ASTNode} node The AST node being checked | ||
* @returns {Boolean} True if node is inside class component's render block, false if not | ||
*/ | ||
function isInsideRenderMethod(node) { | ||
const parentComponent = utils.getParentComponent(); | ||
if (!parentComponent || parentComponent.type !== 'ClassDeclaration') { | ||
return false; | ||
} | ||
|
||
return ( | ||
node.parent | ||
&& node.parent.type === 'MethodDefinition' | ||
&& node.parent.key | ||
&& node.parent.key.name === 'render' | ||
); | ||
} | ||
|
||
/** | ||
* Check whether current node is a stateless component declared inside class component. | ||
* Util's component detection fails to detect stateless components inside class components. | ||
* @returns {Boolean} True if current node a stateless component declared inside class component, false if not | ||
*/ | ||
function isStatelessComponentInsideClassComponent() { | ||
const parentComponent = utils.getParentComponent(); | ||
const parentStatelessComponent = utils.getParentStatelessComponent(); | ||
|
||
return ( | ||
parentComponent | ||
&& parentStatelessComponent | ||
&& parentComponent.type === 'ClassDeclaration' | ||
&& utils.getStatelessComponent(parentStatelessComponent) | ||
); | ||
} | ||
|
||
/** | ||
* Check whether given node is a unstable nested component | ||
* @param {ASTNode} node The AST node being checked | ||
*/ | ||
function validate(node) { | ||
if (!components.get(node) && !isStatelessComponentInsideClassComponent()) { | ||
return; | ||
} | ||
|
||
// Prevent reporting nested class components twice | ||
if (isInsideRenderMethod(node)) { | ||
return; | ||
} | ||
|
||
if (isAnyParentComponent(node)) { | ||
context.report({node, message: ERROR_MESSAGE}); | ||
} | ||
} | ||
|
||
// -------------------------------------------------------------------------- | ||
// Public | ||
// -------------------------------------------------------------------------- | ||
|
||
return { | ||
FunctionDeclaration(node) { validate(node); }, | ||
ArrowFunctionExpression(node) { validate(node); }, | ||
FunctionExpression(node) { validate(node); }, | ||
ClassDeclaration(node) { validate(node); } | ||
}; | ||
}) | ||
}; |
Oops, something went wrong.