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.
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.
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.
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.
<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.
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
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.
Frequently Asked Questions
What happens to Effects when an Activity is hidden?
Does a hidden Activity still render its children?
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.
More in this topic
How to Build a Dropdown Menu in React
Build a React dropdown menu with the ARIA menu button pattern. Handle open and close, keyboard arrows, and clicks outside the menu.
How to Animate React Components with Motion
Animate React components with the Motion library. Set up motion, add enter, hover, and exit animations, and respect reduced motion.
Headless UI Components Explained: Logic Without Locked Styling
Understand headless UI components and how libraries like Radix give you unstyled, accessible behavior that you style yourself.