React 19.2: Activity Component and useEffectEvent Revolutionize Development
Hello HaWkers, React continues to evolve and version 19.2, released in October 2025, brings two new features that promise to change how we build applications: the Activity component and the useEffectEvent hook. These additions solve problems developers have faced for years.
Have you ever struggled with content pre-loading or been frustrated with the complexities of useEffect's dependency array? Then this article is for you.
What's New in React 19.2
This is the third significant release of the year, following React 19 in December 2024 and React 19.1 in June 2025. The React team focused on two fundamental developer experience problems.
Activity Component: The Future of Pre-Rendering
The new <Activity /> component allows you to divide your application into "activities" that can be controlled and prioritized. It's an alternative to traditional conditional rendering, offering two modes: visible and hidden.
Why Activity is Revolutionary
Traditionally, when you need to render content conditionally, you use something like this:
// Traditional approach - component is destroyed and recreated
function Dashboard() {
const [activeTab, setActiveTab] = useState('overview');
return (
<div>
<nav>
<button onClick={() => setActiveTab('overview')}>Overview</button>
<button onClick={() => setActiveTab('analytics')}>Analytics</button>
<button onClick={() => setActiveTab('settings')}>Settings</button>
</nav>
{/* Each tab change destroys the previous component */}
{activeTab === 'overview' && <OverviewPanel />}
{activeTab === 'analytics' && <AnalyticsPanel />}
{activeTab === 'settings' && <SettingsPanel />}
</div>
);
}The problem with this approach is that each tab switch destroys the previous component and creates a new one, losing all internal state and requiring new data requests.
The Solution with Activity
With the Activity component, you can keep components mounted but hidden:
import { Activity } from 'react';
function Dashboard() {
const [activeTab, setActiveTab] = useState('overview');
return (
<div>
<nav>
<button onClick={() => setActiveTab('overview')}>Overview</button>
<button onClick={() => setActiveTab('analytics')}>Analytics</button>
<button onClick={() => setActiveTab('settings')}>Settings</button>
</nav>
{/* Components remain mounted, only visibility changes */}
<Activity mode={activeTab === 'overview' ? 'visible' : 'hidden'}>
<OverviewPanel />
</Activity>
<Activity mode={activeTab === 'analytics' ? 'visible' : 'hidden'}>
<AnalyticsPanel />
</Activity>
<Activity mode={activeTab === 'settings' ? 'visible' : 'hidden'}>
<SettingsPanel />
</Activity>
</div>
);
}
Benefits of the Activity Component
1. State Preservation
Filled inputs, scroll position, and internal state are maintained when you navigate between activities.
2. Intelligent Pre-fetching
You can render hidden parts of the application that the user will likely access:
function ProductPage({ productId }) {
const [showReviews, setShowReviews] = useState(false);
return (
<div>
<ProductDetails id={productId} />
<button onClick={() => setShowReviews(true)}>
View Reviews
</button>
{/* Reviews are loaded in background */}
<Activity mode={showReviews ? 'visible' : 'hidden'}>
<ReviewsSection productId={productId} />
</Activity>
</div>
);
}3. Instant Navigation
Back navigations become instant because the previous state still exists in memory.
useEffectEvent: Goodbye Dependency Hell
The useEffectEvent hook solves one of React's biggest headaches: managing useEffect dependencies when you need updated values without re-running the effect.
The Classic Problem
Consider this common scenario:
// Problem: analytics fires multiple times unnecessarily
function ProductPage({ productId, category, user }) {
useEffect(() => {
// We want to log only when productId changes
// But we need category and user.id for the log
analytics.logProductView({
productId,
category,
userId: user.id,
timestamp: Date.now()
});
}, [productId, category, user.id]); // Problem: fires if category or user changes
return <ProductDetails id={productId} />;
}
Old Solutions (Problematic)
Developers used to use refs to work around this:
// Old solution with refs - works but verbose
function ProductPage({ productId, category, user }) {
const categoryRef = useRef(category);
const userRef = useRef(user);
useLayoutEffect(() => {
categoryRef.current = category;
userRef.current = user;
});
useEffect(() => {
analytics.logProductView({
productId,
category: categoryRef.current,
userId: userRef.current.id,
timestamp: Date.now()
});
}, [productId]);
return <ProductDetails id={productId} />;
}The Solution with useEffectEvent
The new hook makes this elegant and declarative:
import { useEffect, useEffectEvent } from 'react';
function ProductPage({ productId, category, user }) {
// Creates a stable function that always accesses updated values
const logProductView = useEffectEvent(() => {
analytics.logProductView({
productId,
category, // Always the most recent value
userId: user.id, // Always the most recent value
timestamp: Date.now()
});
});
useEffect(() => {
// logProductView doesn't need to be in dependencies
// but always uses the most recent prop values
logProductView();
}, [productId]); // Fires only when productId changes
return <ProductDetails id={productId} />;
}Practical Use Cases for useEffectEvent
1. Event Handlers in Effects
function ChatRoom({ roomId, onMessage }) {
// onMessage can change, but we don't want to reconnect
const handleMessage = useEffectEvent((message) => {
onMessage(message);
});
useEffect(() => {
const connection = createConnection(roomId);
connection.on('message', handleMessage);
connection.connect();
return () => connection.disconnect();
}, [roomId]); // Reconnects only when roomId changes
}
2. Logging and Analytics
function SearchResults({ query, filters, results }) {
const logSearch = useEffectEvent(() => {
analytics.logSearch({
query,
filterCount: Object.keys(filters).length,
resultCount: results.length,
timestamp: Date.now()
});
});
useEffect(() => {
// Logs only when query changes
// but includes updated data from filters and results
logSearch();
}, [query]);
return <ResultsList results={results} />;
}3. Synchronization with External APIs
function VideoPlayer({ videoId, volume, playbackRate }) {
const updatePlayerSettings = useEffectEvent(() => {
// Settings that shouldn't cause video reload
player.setVolume(volume);
player.setPlaybackRate(playbackRate);
});
useEffect(() => {
const player = createPlayer(videoId);
// Apply initial settings
updatePlayerSettings();
return () => player.destroy();
}, [videoId]); // Reloads video only when videoId changes
// Update settings without reloading
useEffect(() => {
updatePlayerSettings();
}, [volume, playbackRate]);
}React Performance Tracks in DevTools
React 19.2 also adds new performance tracks to Chrome DevTools that greatly facilitate debugging.
Scheduler Track
Shows what React is processing at different priorities:
function App() {
const [searchTerm, setSearchTerm] = useState('');
const [results, setResults] = useState([]);
const handleSearch = (term) => {
// High priority - immediate input update
setSearchTerm(term);
// Low priority - search doesn't block UI
startTransition(() => {
const filtered = performExpensiveSearch(term);
setResults(filtered);
});
};
return (
<div>
<input
value={searchTerm}
onChange={(e) => handleSearch(e.target.value)}
/>
<SearchResults results={results} />
</div>
);
}In DevTools, you'll see separate tracks for:
- Blocking: User interactions (high priority)
- Transition: Updates inside startTransition
- Idle: Low priority work
Migration and Compatibility
Updating to React 19.2
The update is simple and has no breaking changes from React 19.1:
# npm
npm install react@19.2.0 react-dom@19.2.0
# yarn
yarn add react@19.2.0 react-dom@19.2.0
# pnpm
pnpm add react@19.2.0 react-dom@19.2.0TypeScript Type Compatibility
For TypeScript, make sure to update the types:
npm install @types/react@19.2.0 @types/react-dom@19.2.0The new types include complete definitions for Activity and useEffectEvent:
import { Activity, useEffectEvent } from 'react';
interface ActivityProps {
mode: 'visible' | 'hidden';
children: React.ReactNode;
}
// useEffectEvent preserves the function type
const handler = useEffectEvent((event: MouseEvent) => {
// event is properly typed
console.log(event.clientX, event.clientY);
});Comparison with Previous Approaches
Activity vs CSS display: none
| Aspect | Activity hidden | CSS display: none |
|---|---|---|
| React Lifecycle | Paused | Continues running |
| Memory | Optimized | State maintained |
| Effects | Don't run | Continue running |
| Re-render | Doesn't occur | Can occur |
useEffectEvent vs useCallback
| Aspect | useEffectEvent | useCallback |
|---|---|---|
| Values | Always updated | Stale if deps change |
| Dependencies | Never listed | Must be listed |
| Use case | Effects | Component props |
| Stability | Always stable | Depends on deps |
Conclusion
React 19.2 represents another important step in the framework's evolution. The Activity component elegantly solves the pre-loading and state preservation problem, while useEffectEvent eliminates years of frustration with dependency arrays.
These new tools aren't just incremental improvements - they fundamentally change how we think about state management and side effects in React applications.
If you're working with React, I recommend starting to experiment with these features in smaller projects to understand their potential. To complement your knowledge about performance, check out our article about ECMAScript 2025 and JavaScript's New Features.

