HomeJournalThis post

React Activity Without Losing Hidden UI State

Retain hidden panel state while effects stop, background work lowers priority, and accessible focus restores.

JP
JP Casabianca
UI/UX designer and full-stack engineer · Bogotá

React Activity solves a precise interface problem: hide a mounted region without throwing away the local state a user expects to recover. It does not mean hidden work should keep subscriptions, focus, animation, or urgent rendering priority.

This tutorial applies Activity to a three-document workspace and tests drafts, scroll, effects, accessibility, restoration, and the remount fallback.

React Activity keeps state, not permission to run

The workspace has tabs for Brief, Evidence, and Draft, and each contains an editor, scrollable references, validation state, and a resize subscription. Unmounting an inactive tab discards an unfinished draft unless state is lifted or persisted. Leaving every panel ordinarily visible-but-covered preserves state but also leaves effects and rendering pressure active. React Activity introduces a third lifecycle: the tree remains represented while hidden behavior changes deliberately.

Start by listing what must survive: controlled input value, undo model owned by the editor, selection, expanded citations, and scroll offset. Then list what must stop: observers, timers, animation, audio, network polling, and focusability. “Mounted” is too coarse to express these responsibilities. The design needs visible and hidden contracts that every child can satisfy.

Hidden UI state is valuable only while its owner remains clear. React Activity should preserve the draft under the document key, while workspace selection, authorization, and server truth continue to live outside the hidden subtree.

Read Activity beside effect semantics

The React Activity reference describes visible and hidden modes, including state preservation and different handling of effects and rendering work. The React 19.2 release provides release context, while the useEffect reference remains the contract for setup, cleanup, and dependency behavior. Pin the supported React version and test actual behavior rather than paraphrasing a release post into an architecture guarantee.

Effects should already be symmetrical before Activity enters the codebase. A resize observer setup returns a cleanup that disconnects the exact observer; a document listener removes the same function; a subscription unsubscribes. Hidden mode will expose leaky effects that appeared harmless when components only mounted once. Fix those lifecycles first so Activity changes visibility, not correctness.

Effect cleanup still follows React's setup-and-cleanup contract. Add counters for subscriptions, timers, observers, and media so the test can prove hidden panels release work and visible panels establish it once. Capture the transition timestamps too.

React Activity visible and hidden lifecycleThree document panels retain draft and scroll state while only the visible panel owns effects, focus, and urgent rendering.BriefEvidenceDraftstate retainedeffects activestate retainedeffects stoppedanchor retainedeffects stoppedfocus here
Figure 1: State remains with each keyed panel; active effects follow visibility instead of mount identity alone.

Model tab selection independently from panel state

The selected document ID belongs to the workspace or URL, while each panel owns its draft state under a stable key. Rendering an Activity boundary per document lets selection change mode without changing identity. Avoid array indexes as keys, because document reorder would attach retained state to the wrong file. When a document is truly closed, remove its boundary and decide whether to persist or discard its draft through an explicit close flow.

React Activity is not a cache eviction policy: ten hidden lightweight forms may be reasonable, while hundreds of editors with retained document models may exhaust memory. Add an upper bound and a visible “recently closed” persistence path. Measure retained heap and hidden update work with realistic documents. The product rule can keep the last three tabs warm while serializing older drafts, giving state continuity without unlimited hidden trees.

State restoration begins with identity, not pixels. Persist a semantic document anchor and selection range under the stable key, then derive scroll position after fonts and dynamic blocks have settled.

Stop effects at the hidden boundary

Place the boundary high enough to contain the subscriptions that belong to the panel, but not so high that shared navigation disappears. When Brief becomes hidden, its resize observer, keyboard shortcut scope, and polling effect should clean up. When it becomes visible, setup runs against current props and retained state. Keep server data in a cache whose lifetime is independent if other visible consumers still need it.

The failure mode is an effect with incomplete cleanup continuing to update a hidden editor. Its consequence is battery use, surprising state, and duplicate listeners after restoration. Inject visibility changes repeatedly in development Strict Mode and count active subscriptions. React Activity passes when the count returns to one for the visible panel and zero for hidden panels, even after errors and rapid tab switches.

The hidden boundary should stop network polling, ResizeObserver work, animations, and global listeners owned by that panel. Shared data may continue elsewhere, but background rendering must not imply duplicate subscriptions.

Use lower priority without hiding correctness bugs

Hidden rendering can occur at lower priority, which helps visible interaction remain responsive when inactive content receives updates, but that is not a guarantee that hidden components do no work. Keep high-frequency streams outside inactive trees or summarize them in a shared store. A hidden panel should render the newest relevant snapshot when resources permit, but visible typing must win. Measure input delay during synthetic background updates instead of assuming the scheduler erases cost.

Do not put required business effects inside rendering or depend on hidden updates completing by a deadline. React rendering can restart or defer work. Save drafts through explicit state transitions and durable services, with status visible in the workspace. Priority is a presentation optimization; data integrity belongs to an idempotent application path that works whether the panel is visible, hidden, or closed.

Record visible input latency while inactive panels receive synthetic updates. React Activity can lower hidden priority, yet the product still needs a budget and a cutoff for expensive editors or high-frequency streams.

StrategyStateEffectsBest fit
UnmountLost or liftedStoppedCheap panels
CSS concealRetainedUsually activePure presentation
Activity hiddenRetainedCleanedWarm workspace
Persist + unmountSerializedStoppedLong absence
Figure 2: Visibility strategies trade state continuity against background cost.

Make accessibility follow visual truth

The active tab uses the tab pattern's selected state and controls the visible panel. Hidden panels must not remain in the sequential focus order or accessibility reading flow. When switching, keep focus on the chosen tab unless the product explicitly moves it into newly revealed content. If focus was inside a panel when another tab becomes active, store a safe restoration target such as editor or citation list, not a stale DOM node.

On return, restore focus only after a user-initiated switch and only to an element that still exists and remains appropriate. Announce validation changes that occurred while hidden when the panel returns, rather than firing live-region noise from invisible work. React Activity should make the accessibility tree tell the same story as the pixels: one active workspace panel, other retained states unavailable until selected.

Accessibility state must match what can be seen and operated. Remove hidden descendants from focus order and reading flow, then restore focus to a meaningful control without announcing every background refresh.

Restore scroll and selection with stable anchors

Browser scroll retention inside hidden regions may vary with component and layout behavior, so treat it as a tested promise. Store a logical anchor such as citation ID and offset when leaving, then restore after layout when returning. For editor selection, use the editor's own serializable position model instead of raw range objects. If content changed while hidden, resolve to the nearest valid anchor and disclose the update.

The useful test changes font size, container width, and inserted content while Draft is hidden. A raw pixel offset can land on the wrong paragraph; an anchor follows meaning. React Activity preserves component state, but the application still defines what restoration means after responsive reflow. This is design work as much as state management because a technically retained coordinate can produce a disorienting return.

Restore by heading ID, record key, or editor position before using raw offsets. Stable anchors survive responsive wrapping and inserted content better than a stored number of pixels from the top.

  1. 1Capture

    Store panel key, focus role, and content anchor.

  2. 2Hide

    Stop effects and remove interaction.

  3. 3Update

    Deprioritize hidden presentation work.

  4. 4Return

    Resolve anchor, restore focus, restart effects.

Figure 3: Restoration follows logical anchors rather than stale DOM coordinates.

Compare hide, unmount, CSS, and persistence

Run four implementations against the same workspace: conditional unmount, CSS concealment, Activity hidden mode, and unmount plus serialized draft restoration. Score draft continuity, effect count, background commits, memory, first-return time, focus, scroll accuracy, and implementation surface. CSS concealment may retain too much activity; unmount may be perfectly adequate for cheap panels; serialization may be best when sessions cross reloads.

Use interaction state foundations, cross-tab coordination, resilient loading states, and View Transition hierarchy as adjacent patterns. My position is to adopt React Activity only when stateful return is a measured product requirement and effect cleanup is already reliable. Novel lifecycle should buy visible continuity, not architecture decoration.

Compare memory, reactivation delay, effect count, and recovery quality for hide, unmount, CSS concealment, and persistence. The result may keep lightweight forms warm while evicting large document models. Repeat the comparison with a long document, a failed request, and assistive technology active so the recommendation reflects production-shaped state.

Release with a restoration transcript

The acceptance script types distinct drafts in three tabs, scrolls each to a named citation, switches rapidly, triggers background data changes, verifies subscription counts, navigates with keyboard and screen reader, closes a document, exceeds the warm-panel limit, changes viewport, and returns. Capture state keys, effect events, focus targets, anchor resolution, hidden commit counts, and heap snapshots without recording private document content.

React Activity succeeds when the interface returns exactly what the user was doing while no hidden panel behaves as an active surface. Preserve stable local state, clean every effect, bound memory, let visible input keep priority, and restore focus and scroll by meaning. Keep the unmount-plus-persistence path understood, because it remains the honest fallback when support, scale, or lifecycle requirements change.

Runnable artifact: The state model proves that draft and scroll values survive hiding while subscriptions clean up and reinstall when the region becomes visible. Save it as activity-state-model.test.mjs and run node activity-state-model.test.mjs. Expected final line: PASS: hidden state restored.

import assert from "node:assert/strict";
const state={draft:"Ada",scroll:420,effects:new Set(["resize"])};
const hide=s=>({...s,mode:"hidden",effects:new Set()});const show=s=>({...s,mode:"visible",effects:new Set(["resize"])});
const restored=show(hide(state));assert.equal(restored.draft,"Ada");assert.equal(restored.scroll,420);assert.deepEqual([...restored.effects],["resize"]);
console.log("PASS: hidden state restored");

Adopt the feature only after the workspace comparison shows a meaningful return advantage over unmount plus persistence. React Activity should retain the exact draft, semantic anchor, and local editing context users need, while hidden panels lose focusability, subscriptions, media, animation, and urgent rendering pressure. Bound the number of warm trees and persist older work so state continuity does not become an unbounded memory policy.

The release transcript should let a reviewer follow one panel from visible to hidden and back: stable key, captured focus role, cleaned effects, deprioritized updates, resolved content anchor, restarted effects, and restored interaction. Repeat it with rapid switching, dynamic content, errors, zoom, and assistive technology. Revisit the architecture when editor weight, React behavior, supported panel count, or restoration expectations change. Preserved state matters only when the person recognizes the place they return to.