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

Add no-nested-components ESLint rule #25360

Closed
wants to merge 5 commits into from
Closed
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
92 changes: 90 additions & 2 deletions packages/eslint-plugin-react-hooks/README.md
Expand Up @@ -41,6 +41,7 @@ If you want more fine-grained configuration, you can instead add a snippet like
],
"rules": {
// ...
"react-hooks/no-nested-components": "error",
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn"
}
Expand Down Expand Up @@ -70,6 +71,93 @@ We suggest to use this option **very sparingly, if at all**. Generally saying, w

Please refer to the [Rules of Hooks](https://reactjs.org/docs/hooks-rules.html) documentation and the [Hooks FAQ](https://reactjs.org/docs/hooks-faq.html#what-exactly-do-the-lint-rules-enforce) to learn more about this rule.

## License
## no-nested-components

MIT
Based of [`react/no-unstable-nested-components`](https://github.com/jsx-eslint/eslint-plugin-react/blob/8beb2aae3fbe36dd6f495b72cb20b27c043aff68/docs/rules/no-unstable-nested-components.md) but with a [detection mechanism consistent with Rules of Hooks](https://reactjs.org/docs/hooks-faq.html#what-exactly-do-the-lint-rules-enforce).

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://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).

### `no-nested-components` details

The following patterns are considered warnings:

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

return (
<div>
<UnstableNestedComponent />
</div>
);
}
Comment on lines +87 to +98
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should all mentions of UnstableNestedComponent be simply just NestedComponent? There is no such thing as stable nested component. When I came up with this term, I falsely thought useCallback or useMemo would "stabilize" it.

Suggested change
function Component() {
// nested component declaration
function UnstableNestedComponent() {
return <div />;
}
return (
<div>
<UnstableNestedComponent />
</div>
);
}
function Component() {
// Nested component declaration
function NestedComponent() {
return <div />;
}
return (
<div>
<NestedComponent />
</div>
);
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. Just copied these tests but I should make a pass regarding naming

```


```jsx
function useComponent() {
// Nested component declaration in a hook. See https://reactjs.org/docs/hooks-faq.html#what-exactly-do-the-lint-rules-enforce for what's considered a Component and hook.
return function Component() {
return <div />
}
}
```

```jsx
function Component() {
const config = React.useMemo({
// Nested component declaration. See https://reactjs.org/docs/hooks-faq.html#what-exactly-do-the-lint-rules-enforce for what's considered a component.
ArrowDown(event) {

}
})

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


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

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

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

⚠️ WARNING ⚠️:

Creating nested but memoized components should also be avoided since memoization is a performance concern not a semantic guarantee.
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(() => <div />, []);

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