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 1
const 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
memocan actually work
Cons
- Long prop lists; pass
childrenor use context {renderRow(row)}helpers are fine but cannot use hooks- Want a fresh instance? Change its
key