PAS7 Studio

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 <Activity /> preview image on a computer hardware background: “State kept. Effects paused.”
Guide / SeriesSeries article

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.

Published

02

useActionState deep dive: mutation flows, optimistic UI, and integration patterns

When useActionState reduces boilerplate and when a data layer still owns the problem.

Planned

03

React <Activity />: keep state, pause Effects, background render

Practical patterns, performance trade-offs, ecosystem pitfalls, and a rollout checklist.

You are here

04

useEffectEvent deep dive: effect design, subscriptions, and analytics

Linter-friendly effect boundaries without stale closures or reconnect churn.

Planned

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]

Section overview screenshot

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]

DOM

hidden

React hides children using display: none while hidden.[1]

State

preserved

React preserves state so UI can restore instantly when visible.[1]

Effects

destroyed

Effects are cleaned up while hidden; re-mounted when visible.[1][2]

Updates

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 screenshot

API baseline

Minimal usage:

TSX
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]

TSX
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.

TSX
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

TSX
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.

TSX
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:

TSX
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

hidden is omitted

The Activity reference notes that UI inside mode="hidden" is not included in the SSR response.[1]

Hydration

priority matters

The reference discusses hydration scheduling implications around visibility and prioritization.[1]

Use case

advanced panels

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:

MD
| 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

Does `hidden` mean React won’t render this subtree at all?

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]

Will my subscriptions/timers continue while hidden?

No. When an Activity is hidden, React destroys Effects (cleanup runs) and mounts them again when it becomes visible.[1][2]

Is Activity a replacement for virtualization (react-window, virtuoso)?

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]

Can Activity help with SSR performance?

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]

What’s the biggest production footgun with Activity?

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]

Is Activity stable enough for production?

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

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.

You are here03/04

React <Activity />: keep state, pause Effects, background render

Related Articles

growthFebruary 15, 2026

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.

Read →
telegram-media-saverJanuary 8, 2025

Automatic Tagging & Search for Saved Links

Integrate with GDrive/S3/Notion for automatic tagging and fast search via search APIs

Read →
servicesJanuary 1, 2025

Bot Development & Automation Services

Professional Telegram bot development and business process automation: chatbots, AI assistants, CRM integrations, workflow automation.

Read →
backend-engineeringFebruary 15, 2026

Bun vs Node.js in 2026: Why Bun Feels Faster (and How to Audit Your App Before Migrating)

Bun is shipping a faster, all-in-one JavaScript toolkit: runtime, package manager, bundler, and test runner. Here’s what’s real (with benchmarks), what can break, and how to get a free migration-readiness audit using @pas7-studio/bun-ready.

Read →

Professional development for your business

We create modern web solutions and bots for businesses. Learn how we can help you achieve your goals.