React.js, part 20: where React is heading — the Activity component and rendering what is offscreen
Part 20from the React.js series · 20 parts in all
The last thread to pull is the one that ties the concurrent work together: React is
learning to keep a tree mounted while hiding it, instead of destroying and rebuilding
it. The Activity component is the clearest expression of that idea — and the
natural place to end a series that began with an interruptible renderer.
Activity: hidden, not unmounted
Switching tabs, collapsing a panel, navigating within a wizard — today those unmount the
inactive tree, throwing away scroll position, form entries, pending state, and DOM. Wrapping
the tree in <Activity mode="hidden"> instead keeps it mounted but
visually hidden and deprioritized:
<Activity mode={tab === 'draft' ? 'visible' : 'hidden'}>
<DraftEditor />
</Activity>
<Activity mode={tab === 'preview' ? 'visible' : 'hidden'}>
<PreviewPane />
</Activity>
Switching tabs becomes a visibility change: no state loss, no re-fetch, and instant switching because the tree already exists. Effects are paused while hidden and resumed on return, which is the part that requires care — an effect that assumed unmount as its only teardown point must still clean up on hide.
Offscreen rendering as a primitive
Underneath, hidden content can still be pre-rendered: React may render it at low priority so it is ready before the user ever shows it. That is the same machinery as transitions and Suspense, pointed at a different goal — instead of "show the old UI while the new one loads", it is "prepare the new UI before the user asks".
What this changes for the design of a component
- Unmount is no longer the only lifecycle end. Cleanup must also be correct when a tree pauses, so effects should be self-contained and idempotent (part 2's lesson, returning in a new form).
- Persistent UI state becomes cheap. Scroll position, open accordions, and half-typed forms survive navigation without you serializing them anywhere.
- Memory becomes the tradeoff. Keeping trees mounted costs memory, so hidden is for bounded, reversible UI — tabs, panels, steps — not for arbitrary history.
The through-line of the whole series
React 18 gave the renderer the ability to interrupt and resume. Everything since has been about exposing that power in datatypes you can reason about: transitions for urgency, Suspense for pending, actions for form lifecycles, optimistic state for latency, the compiler for memoization, and now Activity for visibility. The unifying idea is that a component should describe what the UI is for a given state, and React should be free to decide when — and how much of it — to actually render. That is a much more interesting contract than the one this series started with, and it is why the ordinary props-and-state React you already know still composes cleanly with all of it.