react/no-unstable-nested-components
Disallow defining a component inside another component
A component defined inside another component is a new function every time the parent renders. React compares components by identity, so it sees a different component each time. It removes the old DOM nodes, builds new ones, and throws away the state of everything below. Inputs lose focus and their text, and effects run again.
Move the inner component to the top level of the file and pass it what it needs as props.
Configure#
Off by default. To turn it on:
bunfig.toml
[lint.rules]
"react/no-unstable-nested-components" = "error" # or "warn"Every option, set to its default:
bunfig.toml
[lint.rules]
"react/no-unstable-nested-components" = ["error", {
allowAsProps = false,
propNamePattern = "render*",
customValidators = [], # list of strings
}]Examples#
example.jsx1 problem
function UserList({ users }) {
function Row({ user }) {Do not define components during render. React will see a new component type on every render and destroy the entire subtree’s DOM nodes and state (reactjs.org/docs/reconciliation.html#elements-of-different-types). Instead, move this component definition out of the parent component “UserList” and pass data as props.
return <li>{user.name}</li>;
}
return (
<ul>
{users.map(user => (
<Row key={user.id} user={user} />
))}
</ul>
);
}A component created in a prop is reported too:
example.jsx1 problem
function Page() {
return <Layout footer={() => <small>Terms of use</small>Do not define components during render. React will see a new component type on every render and destroy the entire subtree’s DOM nodes and state (reactjs.org/docs/reconciliation.html#elements-of-different-types). Instead, move this component definition out of the parent component “Page” and pass data as props. If you want to allow component creation in props, set allowAsProps option to true.} />;
}example.jsxNo problems
function Row({ user }) {
return <li>{user.name}</li>;
}
function UserList({ users }) {
return (
<ul>
{users.map(user => (
<Row key={user.id} user={user} />
))}
</ul>
);
}A function passed as children, or in a prop whose name starts with render, counts as a render prop. The receiver calls it as a plain function, so nothing is remounted:
example.jsxNo problems
function Page() {
return <Layout renderFooter={() => <small>Terms of use</small>} />;
}Options#
| Option | Default | What it does |
|---|---|---|
allowAsProps | false | Allow a component that's created in a prop. |
propNamePattern | "render*" | A glob that matches the names of render props. |
customValidators | [] | Accepted, but has no effect. |
example.jsxwith
{ "allowAsProps": true }No problemsfunction Page() {
return <Layout footer={() => <small>Terms of use</small>} />;
}example.jsxwith
{ "propNamePattern": "*Renderer" }No problemsfunction Report({ rows }) {
return <Table rows={rows} rowRenderer={row => <Row data={row} />} />;
}