React Activity: The Secret to Instant UI Transitions
Discover how React's new Activity component revolutionizes conditional rendering. Learn to preserve state, boost performance, and create instant UI transitions in this deep dive.

“The fastest render is the one you don’t have to do.”
We’ve all faced the classic React dilemma: Unmount vs. Hide.
You’re building a tabbed interface. User fills out a form in Tab A, switches to Tab B, and then goes back to Tab A.
- If you used conditional rendering (
{ activeTab === 'A' && <TabA /> }), the component unmounted. The form state is gone. The user is frustrated. - If you used CSS hiding (
<div style={{ display: activeTab === 'A' ? 'block' : 'none' }}><TabA /></div>), the component stays mounted. The state is safe! But… React keeps rendering it. If Tab A is heavy, your app slows down even when the user is looking at Tab B.
For years, we’ve hacked around this with global state stores, keep-alive libraries, or just accepting the performance hit.
Enter React Activity.
Part of the React 19 instructions (specifically highlighted in 19.2+ updates), <Activity> gives us the best of both worlds: the state preservation of “CSS hiding” with the performance benefits of “unmounting.”
What is <Activity>?
The <Activity> component is a primitive that tells React: “This tree is currently not visible, but keep it alive.”
When you wrap a component in <Activity mode="hidden">:
- State is Preserved: React holds onto the component’s state (
useState,useReducer, etc.). - DOM is Hidden: React applies
display: noneto the DOM nodes. - Effects are Unmounted: Unlike simple CSS hiding, React does clean up
useEffectlayout effects. This frees up resources (like subscriptions or timers) while the component is hidden. - Updates are Deprioritized: React knows this content isn’t visible, so it lowers the priority of any updates happening inside it (e.g., if data changes in the background).
How It Works
Let’s look at the API. It accepts a single primary prop: mode.
The Old Way (State Loss)
function App() {
const [tab, setTab] = useState("home");
return (
<div>
<button onClick={() => setTab("home")}>Home</button>
<button onClick={() => setTab("profile")}>Profile</button>
{/* Profile unmounts when you switch to Home. State is lost. */}
{tab === "home" && <Home />}
{tab === "profile" && <Profile />}
</div>
);
}The New Way (Instant Transitions)
import { Activity } from "react";
function App() {
const [tab, setTab] = useState("home");
return (
<div>
<div className="tabs">
<button onClick={() => setTab("home")}>Home</button>
<button onClick={() => setTab("profile")}>Profile</button>
</div>
<div className="content">
{/* Both components stay "mounted" but one is hidden */}
<Activity mode={tab === "home" ? "visible" : "hidden"}>
<Home />
</Activity>
<Activity mode={tab === "profile" ? "visible" : "hidden"}>
<Profile />
</Activity>
</div>
</div>
);
}In this example, if you type into an input in <Profile />, switch to Home, and come back, your text is still there.
Detailed Behavior
1. Effect Lookup
One of the smartest distinct features of <Activity> is how it handles Effects.
- Visible -> Hidden: Effects are cleaned up (unmounted).
- Hidden -> Visible: Effects are re-run (mounted).
This is critical for performance. If your Profile component has a WebSocket connection in a useEffect, you might not want that keeping the connection open when the user is on the Home tab. <Activity> closes it automatically.
2. De-prioritized Rendering
If the Profile component is hidden, but some global data changes that would cause it to re-render, React will mark that update as low priority. It will finish rendering the visible Home tab first, and only update Profile when the main thread has idle time.
Use Cases
1. Instant Modals
Pre-render a heavy modal in the background with mode="hidden". When the user clicks “Open,” switch to mode="visible". It appears instantly because the DOM is already there, just hidden.
2. Virtualized Lists
Keep items that just scrolled off-screen “warm” for a few seconds. If the user scrolls back up, they reappear instantly without re-rendering from scratch.
3. Multi-step Forms
Preserve the state of previous steps without lifting all that state up to a massive parent context or Redux store.
Caveats & “Gotchas”
- Text Nodes:
<Activity>needs a stable DOM element to applydisplay: noneto. - Tree Size: Even if updates are deprioritized, the component is in memory. Don’t wrap your entire application in 50 hidden Activity providers.
- Focus Management: When a component becomes hidden, you might need to ensure focus moves to a visible element if the focus was inside the hidden tree.
Conclusion
React Activity solves the “Unmount vs. Hide” trade-off. It unifies the developer experience of conditional rendering with the user experience of persistent state.
By giving us a native way to mark content as “background,” React allows us to build interfaces that feel instant and solid, without the manual state management overhead.