React Activity Explained: Hide and Restore UI State

The Activity component hides a part of the UI without unmounting it, keeping its state and DOM while its Effects are cleaned up until it becomes visible again.

7 min read

The React Activity component hides a part of the UI without unmounting it. Its children keep their state and DOM while hidden, but their Effects are cleaned up, so hidden content stops doing background work until it becomes visible again.

Stable in React 19.2

Activity shipped as a stable feature in React 19.2. Hidden children unmount their Effects and defer updates, while visible children run normally.

Hide without losing state

Conditional rendering unmounts a component and destroys its state, so reopening a panel always starts from scratch. Activity hides the same content instead, keeping its state and DOM alive so the round trip is free.

App.jsxApp.jsx
import { Activity, useState } from "react";
 
export default function App() {
  const [showSidebar, setShowSidebar] = useState(true);
  return (
    <div>
      <Activity mode={showSidebar ? "visible" : "hidden"}>
        <Sidebar />
      </Activity>
      <button onClick={() => setShowSidebar(!showSidebar)}>
        Toggle sidebar
      </button>
    </div>
  );
}

When the sidebar hides, React applies display none and cleans up its Effects. Toggling it back restores the previous state, so an expanded section inside the sidebar stays expanded. This is the difference between hiding and unmounting: hiding keeps the component mounted in the background, while unmounting throws away everything it remembered.

The mode prop defaults to visible, so an Activity without a mode renders its children normally. That also means Activity is safe to introduce around existing content before you add any hiding logic.

Effects are destroyed while hidden

A hidden Activity behaves conceptually like an unmounted component: its subscriptions and timers stop, which prevents hidden UI from doing background work.

App.jsxApp.jsx
import { useEffect } from "react";
 
export default function Notifications() {
  useEffect(() => {
    const source = subscribeToNotifications();
    return () => source.unsubscribe();
  }, []);
  return <p>Live notifications</p>;
}

The cleanup runs when Notifications becomes hidden and reconnects when it returns. Components with proper Effect cleanup already work with Activity without changes. Hidden children still re-render in response to new props, but at a lower priority than visible content, so the app stays responsive.

Strict Mode already runs cleanup and setup cycles in development, which surfaces missing cleanup early. A component that survives Strict Mode will also survive being hidden.

Pre-render content before it is needed

A hidden Activity still renders its children at a lower priority without mounting their Effects. That lets code and data load ahead of time, so the reveal is instant.

App.jsxApp.jsx
<Activity mode={activeTab === "posts" ? "visible" : "hidden"}>
  <Posts />
</Activity>

The Posts tab loads its data while still hidden, so clicking it skips the Suspense fallback. Only a suspending source, like a Promise read with use, fetches during this pre-render.

This is useful for tabs the user is likely to open next, because the data is already there when they click. See The React use API for that pattern.

The pre-render runs at a lower priority, so it never starves the visible content of render time. When the boundary flips to visible, the content appears immediately instead of mounting fresh.

Selective hydration

Activity boundaries divide the tree into independent hydration units, much like Suspense boundaries. The tab buttons can become interactive before the hidden or slow tabs finish hydrating, which keeps the first paint responsive.

Even an always-visible Activity boundary improves hydration, because it tells React which parts of the page can become interactive in isolation. See How Streaming SSR Works with React Suspense for the server side of that flow.

This is why Activity is useful even when nothing is ever hidden: it splits the page into units that hydrate independently.

Cleanup for media elements

Hidden Activity keeps the DOM, so a video or audio element keeps playing unless you pause it in an Effect cleanup.

App.jsxApp.jsx
import { useLayoutEffect, useRef } from "react";
 
export default function Video() {
  const ref = useRef(null);
  useLayoutEffect(() => {
    const video = ref.current;
    return () => video.pause();
  }, []);
  return <video ref={ref} controls src="/intro.mp4" />;
}

The cleanup pauses playback when the boundary hides, while the DOM survives so the timecode is preserved. useLayoutEffect fits here because the cleanup is tied to the visual hiding.

The most common offenders are video, audio, and iframe elements, which keep running while their DOM stays in the page. When Activity is visible again, React ViewTransition can animate that reveal.

Think of a hidden Activity as unmounted for side effects but mounted for state, which is the rule that explains most surprises. That balance is what makes tabs and sidebars feel instant without losing their place.

Rune AI

Rune AI

Key Insights

  • Activity hides content and keeps its state and DOM.
  • Hidden children lose their Effects until shown again.
  • It can pre-render content before the user sees it.
  • Activity boundaries participate in selective hydration.
  • Media tags need Effect cleanup when hidden.
RunePowered by Rune AI

Frequently Asked Questions

What happens to Effects when an Activity is hidden?

React cleans up the children's Effects, so subscriptions stop while hidden. When the boundary becomes visible again, the Effects are re-created.

Does a hidden Activity still render its children?

Yes, but at a lower priority and without mounting Effects. A hidden Activity that only renders plain text produces no DOM output.

Conclusion

Activity hides content without unmounting it, so state and DOM survive while Effects are cleaned up. Use it for tabs, sidebars, and pre-rendering content the user will likely open next.