Technology
React <Activity /> deep dive: keep state, pause Effects, background render — practical patterns, real edge cases, and rollout checklist
<Activity /> (React 19.2) is the third option between unmounting UI and CSS-hiding it: it preserves state, hides DOM, destroys Effects, and deprioritizes hidden updates. This chapter (part of the React 2026 Primitives guide) focuses on real patterns for tabs/drawers/shell UIs, pre-rendering the next screen, SSR/hydration implications, ecosystem pitfalls, and a production rollout checklist.

React 2026 Primitives & Compiler Upgrade Guide
A focused chapter on <Activity />. Read the overview first, then adopt patterns incrementally with measurable guardrails.
All articles in this guide
01
Overview: React 2026 primitives and compiler-era mental model
Big-picture architecture shifts, what changed, and where each primitive fits.
02
useActionState deep dive: mutation flows, optimistic UI, and integration patterns
When useActionState reduces boilerplate and when a data layer still owns the problem.
03
React <Activity />: keep state, pause Effects, background render
Practical patterns, performance trade-offs, ecosystem pitfalls, and a rollout checklist.
04
useEffectEvent deep dive: effect design, subscriptions, and analytics
Linter-friendly effect boundaries without stale closures or reconnect churn.
Part of the React 2026 guide
In the overview we explain how Actions, <Activity />, useEffectEvent, Performance Tracks, SSR batching, and the Compiler fit together as a direction (not random features).[2]
This is a chapter from our React 2026 Primitives guide. If you want the roadmap first, start with the overview:
What you’ll get from this chapter
Docs are explicit: when hidden, React hides children using display: none, destroys Effects (cleanup subscriptions), preserves state, and children can still re-render on new props (lower priority).[1]
<Activity /> is best understood as lifecycle + scheduling control for hidden UI. This chapter is about using it without making your app worse.
• The real semantics of
mode="visible" | "hidden": DOM, state, Effects, update priority.[1][2]• Real patterns that ship well: tabs/drawers, “warm next screen”, heavy panels, SSR gating.[1][2]
• How to keep hidden work cheap (prop churn control) and avoid accidental expensive updates.[1]
• Ecosystem edge cases: what breaks in third-party widgets and how to defend yourself.[7]
• A production rollout checklist (feature flags + metrics + memory budget).
React 19.2: hidden hides children, unmounts Effects, defers updates, and enables background preparation.[2]
Why <Activity /> exists: the gap between unmounting and CSS hiding
Most apps historically used:
1) Conditional rendering: you get a clean lifecycle, but you lose local state when hidden UI unmounts (forms, scroll position, ephemeral UI state).
2) CSS hide: state stays, but hidden subtrees can keep subscriptions/timers and compete for main-thread time.
React 19.2 frames Activity as a way to split an app into “activities” that can be controlled and prioritized. In hidden, it hides children, unmounts Effects, and defers updates until React has nothing more important to do.[2]
Mental model: what `hidden` does (and doesn’t)
The most common mistake: assuming hidden means “React doesn’t render”. It can still re-render — but with lower priority.
React Labs clarifies the intent: conceptually the component is “unmounted”, but React saves state for later. They recommend <StrictMode> to find problematic Effects early because Activity will mount/unmount Effects as you toggle.[3]
React Reference: hidden hides DOM, destroys Effects, deprioritizes updates; visible restores previous state and recreates Effects.[1]
Section mental-model screenshotAPI baseline
Minimal usage:
import { Activity, useState } from "react";
export function SidebarShell() {
const [open, setOpen] = useState(false);
return (
<>
<button onClick={() => setOpen((v) => !v)}>
{open ? "Hide" : "Show"}
</button>
<Activity mode={open ? "visible" : "hidden"}>
<Sidebar />
</Activity>
</>
);
}From the official reference: while hidden, Effects are destroyed (cleanup runs), children are hidden via display: none, and children may still re-render on prop changes at lower priority.[1]
Pattern 1: Tabs / drawers that keep state without hidden side-effects
This is the “killer” use case: preserve state (form inputs, scroll) but don’t keep subscriptions/timers alive while hidden.[1][2]
import { Activity } from "react";
export function SettingsTabs({ tab }: { tab: "profile" | "billing" }) {
return (
<>
<Activity mode={tab === "profile" ? "visible" : "hidden"}>
<ProfileTab />
</Activity>
<Activity mode={tab === "billing" ? "visible" : "hidden"}>
<BillingTab />
</Activity>
</>
);
}If a tab needs continuous background work to remain correct, don’t put that engine inside the Activity boundary (we’ll cover that failure mode below).
Pattern 2: “Warm the next screen” on intent (hover/focus) without blocking the foreground
React 19.2 explicitly states you can pre-render and keep rendering hidden parts without impacting visible performance.[2] The trick is to start warmup on *intent* (hover/focus), not on click.
import { Activity, useEffect, useState } from "react";
import { useRouter } from "next/navigation";
export function ContinueButton({ nextHref }: { nextHref: string }) {
const router = useRouter();
const [armed, setArmed] = useState(false);
useEffect(() => {
if (!armed) return;
router.prefetch(nextHref);
}, [armed, nextHref, router]);
return (
<>
<button onMouseEnter={() => setArmed(true)} onFocus={() => setArmed(true)}>
Continue
</button>
<Activity mode={"hidden"}>
<NextStepShell />
</Activity>
</>
);
}This pattern is intentionally conservative: it prepares the subtree in the background, but never shows it until navigation. It’s a scheduling hint, not a UI trick.
Pattern 3: Keep hidden renders cheap by controlling prop churn at the boundary
import { Activity, useMemo } from "react";
type Filters = { key: string; q: string; sort: "new" | "top" };
export function HeavyPanelHost(props: {
visible: boolean;
filters: Filters;
liveTick: number;
}) {
const stableFilters = useMemo(() => props.filters, [props.filters.key]);
return (
<Activity mode={props.visible ? "visible" : "hidden"}>
<HeavyPanel filters={stableFilters} />
</Activity>
);
}Hidden children can still re-render on prop changes.[1] In real apps, the main cost is not Activity itself — it’s you feeding constantly changing props into hidden trees.
• Avoid passing high-frequency values (
now, streaming stats, scroll position) into hidden trees.• Stabilize props that don’t need to change while hidden (memoize by a stable key).
• If you must pass live values, pass them into a small stable engine and derive a coarse snapshot for the hidden subtree.
Pattern 4: Don’t hide your data engine inside Activity
This is the most common production footgun: Activity destroys Effects while hidden.[1][2] If correctness relies on continuous subscriptions/polling, move the engine outside Activity and feed data in.
import { Activity, useEffect, useState } from "react";
type Live = { count: number };
declare function subscribeLive(onValue: (v: Live) => void): () => void;
function LiveEngine({ onValue }: { onValue: (v: Live) => void }) {
useEffect(() => subscribeLive(onValue), [onValue]);
return null;
}
export function ScreenWithPanel({ open }: { open: boolean }) {
const [live, setLive] = useState<Live>({ count: 0 });
return (
<>
<LiveEngine onValue={setLive} />
<Activity mode={open ? "visible" : "hidden"}>
<Panel live={live} />
</Activity>
</>
);
}This design keeps correctness independent from the panel visibility and avoids “it stopped updating when hidden” bugs.
SSR / hydration implications (where Activity becomes an architecture tool)
Activity isn’t only about UX state retention. It’s also an SSR lever: what you include in initial HTML, and what you intentionally defer.
A practical SSR gating example:
import { Activity, useState } from "react";
export function Page() {
const [advanced, setAdvanced] = useState(false);
return (
<>
<PrimaryContent />
<button onClick={() => setAdvanced((v) => !v)}>Advanced</button>
<Activity mode={advanced ? "visible" : "hidden"}>
<AdvancedPanel />
</Activity>
</>
);
}SSR response
The Activity reference notes that UI inside mode="hidden" is not included in the SSR response.[1]
Hydration
The reference discusses hydration scheduling implications around visibility and prioritization.[1]
Use case
Keep truly non-critical UI out of initial HTML to reduce payload and improve perceived load.
ViewTransition interaction: showing/hiding Activity can trigger animations
The Activity reference includes a caveat: when Activity is inside ViewTransition, showing/hiding can trigger enter/exit animations depending on how the update is performed.[1]
If you’re building a navigation shell with transitions, test Activity toggles specifically under startTransition and your chosen ViewTransition usage patterns (timing and animation boundaries matter).
Real ecosystem edge cases: what breaks and how to defend yourself
If a third-party component stores interactive state internally but re-initializes on mount, Activity toggles may expose it. In those cases you typically need one of: (a) externalize state (controlled props), (b) patch/upgrade the library, (c) avoid Activity around that subtree.
The fastest way to lose trust in a new primitive is to ship it into a third-party component that wasn’t designed for its lifecycle semantics.
• deck.gl: an issue report where hiding a <DeckGL> tree via Activity causes view state to reset after showing again.[7]
• Keep-alive prior art helps you reason about eviction, cache budgets, and “what should be preserved.”[8]
• Safe rollout strategy: start with owned UI (tabs/drawers/forms), then expand into third-party heavy widgets after you prove state restoration works.
Production rollout checklist (what we actually do on client projects)
A simple KPI table you can paste into a PR description:
| Metric | Baseline | After Activity | Notes |
|---|---:|---:|---|
| INP (p75) | | | toggle tabs/drawer |
| Long tasks > 50ms | | | navigation + warmup |
| JS heap after 5 toggles | | | memory budget check |
| CPU time during toggle | | | prop churn detection |Activity is worth it when it improves perceived UX without stealth memory/CPU regressions. Treat it like a feature rollout, not a refactor.
• Feature flag the new boundary and roll out gradually (per route / per user segment).
• Measure: INP, long tasks, memory growth (especially if many activities remain hidden).
• Add
<StrictMode>in dev/staging to surface unexpected effect assumptions.[3]• Define a memory budget: how many hidden “screens” you allow at once (avoid accidental keep-alive everywhere).
• Test Activity toggles with: Suspense boundaries, navigation transitions, and third-party heavy widgets.
FAQ
No. The Activity reference explicitly says children can still re-render in response to new props, but at a lower priority than visible content.[1]
No. When an Activity is hidden, React destroys Effects (cleanup runs) and mounts them again when it becomes visible.[1][2]
No. Virtualization reduces DOM/render work for large lists. Activity is about lifecycle/scheduling for whole UI subtrees like tabs, drawers, and screens.[1][2]
Yes, as an intentional lever: the Activity reference notes that UI inside `mode="hidden"` is not included in the SSR response, which can reduce initial HTML for non-critical panels.[1]
Hiding a subtree that contains a ‘data engine’ implemented as Effects (subscriptions/polling). Hidden destroys Effects, so correctness can break unless the engine is moved outside the Activity boundary.[1][2]
It ships in React 19.2 with documented behavior and an official reference.[1][2] React Labs recommends StrictMode during adoption to catch unexpected side effects when Effects are mounted/unmounted via Activity toggles.[3]
Sources
• React Reference: <Activity> (DOM hiding, Effects cleanup, low-priority re-renders, SSR notes) Read source ↗
• React 19.2 (official): Activity semantics and goals Read source ↗
• React Labs: View Transitions, Activity, and more (StrictMode guidance; concept: unmounted but state preserved) Read source ↗
• React Learn: Preserving and Resetting State (baseline mental model for conditional rendering) Read source ↗
• LogRocket: React 19.2 is here (Activity + useEffectEvent overview and practical framing) Read source ↗
• This Week In React #252 (context roundup: React 19.2, Activity, useEffectEvent) Read source ↗
• deck.gl issue: Activity compatibility bug (state resets after hide/show) Read source ↗
• Prior art: React keep-alive patterns (state preservation, caching, eviction strategies) Read source ↗
Want to adopt Activity without regressions?
PAS7 Studio helps teams adopt React 19.2 primitives safely: Activity, Suspense, transitions, and Next.js prefetch patterns — with measurable before/after metrics.
You’ll get a concrete rollout plan: where Activity helps, where it hurts, and how to keep memory + CPU under control.
React <Activity />: keep state, pause Effects, background render