Define components at top level

A component declared inside another remounts on every render.

From vercel-react-best-practices, rule rerender-no-inline-components.

Inputs lose focus

Type a name into both fields.

Defined inside parentBad
<CustomerPicker>

mounts 1focus idle
Module level, propsGood
<CustomerPicker>

mounts 1focus idle
function SearchField({ value, onChange }) {
  const [keystrokes, setKeystrokes] = useState(0)
  return (
    <label>
      Search customers
      <input
        value={value}
        onChange={(e) => {
          setKeystrokes((n) => n + 1)
          onChange(e.target.value)
        }}
      />
    </label>
  )
}

function CustomerPicker({ customers }) {
  const [query, setQuery] = useState('')
  const matches = customers.filter((c) => c.name.includes(query))
  return (
    <>
      <SearchField value={query} onChange={setQuery} />
      <Results items={matches} />
    </>
  )
}

Animations restart from zero

Bump the parent, or turn on auto re-render.

Defined inside parentBad
<Dashboard count={0}>
mounts 1const Spinner = () => …
Module level, propsGood
<Dashboard count={0}>
mounts 1<Spinner label={label} />
function Spinner({ label }) {
  const mountedAt = useRef(performance.now())
  return <svg className="animate-spin" role="img" aria-label={label}>…</svg>
}

function Dashboard() {
  const [count, setCount] = useState(0)
  return (
    <>
      <Spinner label="Syncing" />
      <button onClick={() => setCount((n) => n + 1)}>Count +1</button>
    </>
  )
}

Scroll resets, effects rerun

Scroll both lists, then wait for the clock to tick.

Defined inside parentBad
<Ledger now="--:--:--">
    mounts 1setups 1cleanups 0scroll 0 px
    Module level, propsGood
    <Ledger now="--:--:--">
      mounts 1setups 1cleanups 0scroll 0 px
      function Stats({ rows, asOf }) {
        useEffect(() => {
          const unsubscribe = feed.subscribe()
          return unsubscribe
        }, [])
        return (
          <section>
            <p>As of {asOf}</p>
            <ul className="overflow-y-auto">
              {rows.map((r) => <Row key={r.id} {...r} />)}
            </ul>
          </section>
        )
      }
      
      function Ledger({ rows }) {
        const now = useClock(1000)
        return <Stats rows={rows} asOf={now} />
      }

      Why it matters, pros and cons

      Why it matters

      An inner component is a new type each render, so React remounts it.

      Pros

      • State, focus, scroll and animations survive
      • Effects run on mount, not every render
      • memo can actually work

      Cons

      • Long prop lists; pass children or use context
      • {renderRow(row)} helpers are fine but cannot use hooks
      • Want a fresh instance? Change its key