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, and render in the background
Real patterns, performance trade-offs, and pitfalls for tabs/drawers/shell UIs.
04
useEffectEvent deep dive: effect design, subscriptions, and analytics
Linter-friendly effect boundaries without stale closures or reconnect churn.
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:
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]
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]
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]
hidden
React hides children using display: none while hidden.[1]
preserved
React preserves state so UI can restore instantly when visible.[1]
deprioritized
Hidden children can re-render on new props at lower priority.[1]
React Reference: hidden hides DOM, destroys Effects, deprioritizes updates; visible restores previous state and recreates Effects.[1]
Section mental-model screenshotMinimal 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]
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).
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.
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.
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.
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>
</>
);
}hidden is omitted
The Activity reference notes that UI inside mode="hidden" is not included in the SSR response.[1]
priority matters
The reference discusses hydration scheduling implications around visibility and prioritization.[1]
advanced panels
Keep truly non-critical UI out of initial HTML to reduce payload and improve perceived load.
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).
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.
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.
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]
• React Reference: <Activity> (DOM hiding, Effects cleanup, low-priority re-renders, SSR notes)
• React Learn: Preserving and Resetting State (baseline mental model for conditional rendering)
• LogRocket: React 19.2 is here (Activity + useEffectEvent overview and practical framing)
• This Week In React #252 (context roundup: React 19.2, Activity, useEffectEvent)
• deck.gl issue: Activity compatibility bug (state resets after hide/show)
• Prior art: React keep-alive patterns (state preservation, caching, eviction strategies)
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, and render in the background
Related Articles
AI SEO / GEO in 2026: Your Next Customers Aren’t Humans — They’re Agents
Search is shifting from clicks to answers. Bots and AI agents crawl, cite, recommend, and increasingly buy. Learn what AI SEO / GEO means, why classic SEO is no longer enough, and how PAS7 Studio helps brands win visibility in the agentic web.
The most powerful Apple chip yet? M5 Pro and M5 Max are breaking records
A data-backed March 2026 analysis of Apple M5 Pro and M5 Max. We break down why these chips can credibly be called Apple's most powerful pro laptop silicon, how they compare with M4 Pro, M4 Max, M1 Pro, M1 Max, and how they stack up against Intel and AMD laptop rivals.
Artemis II and the Code That Carries Humans to the Moon
This article unpacks NASA's Artemis II mission, launched on April 1, 2026, and explains what it really says about modern engineering: flight software, backup logic, simulation, telemetry, human control, and the careful role AI can play in space systems.
Automatic Tagging & Search for Saved Links
Integrate with GDrive/S3/Notion for automatic tagging and fast search via search APIs
Professional development for your business
We create modern web solutions and bots for businesses. Learn how we can help you achieve your goals.