React Activity Component
Sun Jun 22 2025
Note:
This component is still not stable. To use it, refer to the docs .
This new React component lets you hide part of the UI while keeping its state in the background, without mounting effects.
This helps pre-render a component that will be needed in the future.
It takes props like children
and mode
. The mode
prop is optional and defaults to visible
.
<Activity mode={"visible" | "hidden"}>
<Page />
</Activity>
We wrap the child component in the Activity component to manage its visibility state. When hidden
, the child is not visible in the UI.
When this component initially renders on the UI, it pre-renders the child in the background at a lower priority, but it does not mount or trigger the effects of the child components.
When an Activity switches from "visible" to "hidden", it destroys the effects but keeps the state. It calls all effect cleanups, allowing a fast switch without recreating the state.
Let's understand why we may need this component with an example. In this example, we have two buttons that switch the video. We conditionally render different video components, but when we switch, the component unmounts and its state is cleared. As a result, the video is reloaded by the browser and plays from the start, which is not intuitive.
import { useState, useRef, useEffect } from "react";
function VideoPlayer({ src }) {
const ref = useRef(null);
useEffect(() => {
const videoRef = ref.current;
videoRef.play();
return () => videoRef.pause();
}, []);
return <video ref={ref} src={src} muted loop playsInline />;
}
export default function App() {
const [video, setVideo] = useState(1);
return (
<>
<div>
<button onClick={() => setVideo(1)}>Video 1</button>
<button onClick={() => setVideo(2)}>Video 2</button>
</div>
{video === 1 && (
<VideoPlayer
key={1}
src="https://somevideo1-url.mp4"
/>
)}
{video === 2 && (
<VideoPlayer
key={2}
src="https://somevideo2-url.mp4"
/>
)}
</>
);
}
In the above code, when the video switches, the VideoPlayer
component for that video unmounts, removing all its state from memory. So when it's switched back, it loads from the start, which is not ideal.
To fix this, we could render both components and simply hide them from the UI using styles like display: none
and display: block
. This would preserve their state, but both videos would play at the same time in the background, which doesn't solve the issue either.
The Activity component fixes this issue. Conceptually, you can think of it as mounting and unmounting, but saving the React or DOM state for later use.
Here is the code using the Activity component:
import { useState, useRef, useEffect, unstable_Activity as Activity } from 'react';
function VideoPlayer({ src }) {
const ref = useRef(null);
useEffect(() => {
const videoRef = ref.current;
videoRef.play();
return () => videoRef.pause();
}, []);
return <video ref={ref} src={src} muted loop playsInline/>;
}
export default function App() {
const [video, setVideo] = useState(1);
return (
<>
<div>
<button onClick={() => setVideo(1)}>Video 1</button>
<button onClick={() => setVideo(2)}>Video 2</button>
</div>
<Activity mode={video === 1 ? 'visible' : 'hidden'}>
<VideoPlayer
key={1}
src="https://somevideo1-url.mp4"
/>
</Activity>
<Activity mode={video === 2 ? 'visible' : 'hidden'}>
<VideoPlayer
key={2}
src="https://somevideo2-url.mp4"
/>
</Activity>
</>
);
}
Hence, although still unstable, Activity enables seamless UI transitions by preserving component state without keeping effects active.