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 }) {
    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>} />;
}
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#

OptionDefaultWhat it does
allowAsPropsfalseAllow 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 problems
function Page() {
  return <Layout footer={() => <small>Terms of use</small>} />;
}
example.jsxwith { "propNamePattern": "*Renderer" }No problems
function Report({ rows }) {
  return <Table rows={rows} rowRenderer={row => <Row data={row} />} />;
}