React 19.2's Activity Component: Not All That Glitters is Gold
React 19.2 introduced the new <Activity /> component, and while it's powerful for preserving UI state while hiding components, there are some important trade-offs you should know about:
โ ๏ธ Key Drawbacks
1. Memory Cost (~2x)
Keeping components hidden means keeping them in memory. Multiple hidden activities = potential memory bloat.
2. Hidden โ Unmounted
The DOM persists when hidden, meaning:
- Video/audio elements keep playing
- Event listeners stay active
- Animations continue running
3. Cleanup is Critical
Your Effects MUST have proper cleanup functions, or you'll face memory leaks and unintended side effects.
4. Not for Everything
Don't reach for the Activity component for simple show/hide logic. It's designed for specific use cases like:
- Tab systems where state preservation matters
- Pre-rendering content users will navigate to next
- Maintaining form state during navigation
๐ก Bottom Line
The Activity component is a specialized tool. Use it strategically, not universally. When you do use it, ensure your components have robust Effect cleanup.
Code Examples: Common Problems
Here's sample code demonstrating the common problems with the Activity component:
โ PROBLEM 1: Video/Audio keeps playing when hidden
import { Activity, useState, useEffect } from 'react';
function VideoPlayer() {
return (
<div>
<h3>Video Player</h3>
<video controls autoPlay>
<source src="sample-video.mp4" type="video/mp4" />
</video>
<p>โ Problem: Video audio continues even when hidden!</p>
</div>
);
}
โ PROBLEM 2: Event listeners stay active
function ProblematicCounter() {
const [count, setCount] = useState(0);
useEffect(() => {
const handleKeyPress = (e) => {
if (e.key === 'ArrowUp') {
setCount((c) => c + 1);
}
};
window.addEventListener('keydown', handleKeyPress);
// โ Missing cleanup function!
// Event listener stays active even when hidden
}, []);
return (
<div>
<h3>Counter: {count}</h3>
<p>Press Arrow Up (event listener stays active when hidden)</p>
</div>
);
}
โ PROBLEM 3: Timers/Intervals keep running
function ProblematicTimer() {
const [time, setTime] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setTime((t) => t + 1);
console.log('Timer running...'); // Logs even when hidden!
}, 1000);
// โ Missing cleanup - interval keeps running
}, []);
return (
<div>
<h3>Timer: {time}s</h3>
<p>โ Timer keeps running in background when hidden</p>
</div>
);
}
โ SOLUTION: Proper cleanup
function FixedTimer() {
const [time, setTime] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setTime((t) => t + 1);
console.log('Fixed timer running...');
}, 1000);
// โ
Proper cleanup function
return () => {
clearInterval(interval);
console.log('Timer cleaned up!');
};
}, []);
return (
<div>
<h3>Fixed Timer: {time}s</h3>
<p>โ
Timer stops when hidden</p>
</div>
);
}
โ PROBLEM 4: Multiple hidden activities = memory bloat
function HeavyComponent({ id }) {
const [data] = useState(() => {
// Simulating heavy data
return new Array(100000).fill(0).map((_, i) => ({
id: i,
value: Math.random(),
nested: { deep: { data: 'expensive' } }
}));
});
return (
<div>
<h4>Heavy Component {id}</h4>
<p>Contains {data.length} items (large memory footprint)</p>
</div>
);
}
Main App demonstrating all problems
function App() {
const [activeTab, setActiveTab] = useState('tab1');
return (
<div style={{ padding: '20px' }}>
<h1>React Activity Component - Problems Demo</h1>
{/* Tab Navigation */}
<div style={{ marginBottom: '20px' }}>
<button onClick={() => setActiveTab('tab1')}>Tab 1 (Video)</button>
<button onClick={() => setActiveTab('tab2')}>Tab 2 (Bad Counter)</button>
<button onClick={() => setActiveTab('tab3')}>Tab 3 (Bad Timer)</button>
<button onClick={() => setActiveTab('tab4')}>Tab 4 (Fixed Timer)</button>
<button onClick={() => setActiveTab('tab5')}>Tab 5 (Memory Bloat)</button>
</div>
{/* Problem 1: Video keeps playing */}
<Activity mode={activeTab === 'tab1' ? 'visible' : 'hidden'}>
<VideoPlayer />
</Activity>
{/* Problem 2: Event listeners stay active */}
<Activity mode={activeTab === 'tab2' ? 'visible' : 'hidden'}>
<ProblematicCounter />
</Activity>
{/* Problem 3: Timers keep running */}
<Activity mode={activeTab === 'tab3' ? 'visible' : 'hidden'}>
<ProblematicTimer />
</Activity>
{/* Solution: Proper cleanup */}
<Activity mode={activeTab === 'tab4' ? 'visible' : 'hidden'}>
<FixedTimer />
</Activity>
{/* Problem 4: Memory bloat with multiple hidden components */}
<Activity mode={activeTab === 'tab5' ? 'visible' : 'hidden'}>
<div>
<h3>Memory Bloat Example</h3>
<p>All 5 heavy components below stay in memory when hidden!</p>
{[1, 2, 3, 4, 5].map((id) => (
<HeavyComponent key={id} id={id} />
))}
</div>
</Activity>
</div>
);
}
โ Fixed Video Example with Proper Cleanup
import { useRef, useEffect } from 'react';
// โ
FIXED: Video with proper cleanup
function FixedVideoPlayer() {
const videoRef = useRef(null);
useEffect(() => {
// Cleanup function pauses video when component is hidden
return () => {
if (videoRef.current) {
videoRef.current.pause();
}
};
}, []);
return (
<div>
<h3>Fixed Video Player</h3>
<video ref={videoRef} controls autoPlay>
<source src="sample-video.mp4" type="video/mp4" />
</video>
<p>โ
Video pauses when hidden!</p>
</div>
);
}
๐งช Testing the Problems
Try this:
- Go to Tab 1, play the video, then switch tabs - audio continues!
- Go to Tab 2, press Arrow Up, switch tabs, keep pressing - counter still updates!
- Go to Tab 3, open console, switch tabs - timer still logs!
- Go to Tab 4 - this one properly stops when hidden โ
- Go to Tab 5, open DevTools Memory profiler - see the memory usage!
What's your take? Have you tried the new Activity component yet?
