React 19.2: Activity Component and Partial Pre-rendering Change Everything
Hello HaWkers, React 19.2 was released in October 2025 and brought features that fundamentally change how we build React applications. The Activity component and Partial Pre-rendering are game-changers.
Let's analyze each new feature and how to apply them in your next project.
The Evolution of React 19
Since December 2024, React 19 has evolved rapidly. Here's the timeline:
Release Timeline
React 19 versions:
| Version | Date | Highlights |
|---|---|---|
| v19.0.0 | December 2024 | Server Components, Actions |
| v19.1.0 | March 2025 | Performance improvements |
| v19.2.0 | October 2025 | Activity, PPR, useEffectEvent |
React 19 completes React's transition to an async-first architecture, the biggest change since React Hooks.
Activity Component: Smart Pre-rendering
What is Activity
The Activity component allows pre-rendering and keeping hidden parts of the application rendered without impacting the performance of what's visible.
Use cases:
- Pre-render routes: Pages the user will likely access
- Preserve state: Maintain state of navigated parts
- Smooth transitions: Navigate without loading
- UX optimization: Instant experience
How It Works
import { Activity } from 'react';
function App() {
const [currentTab, setCurrentTab] = useState('home');
return (
<div>
<TabBar current={currentTab} onChange={setCurrentTab} />
{/* Active tab - renders normally */}
<Activity mode={currentTab === 'home' ? 'visible' : 'hidden'}>
<HomeTab />
</Activity>
{/* Hidden tab - pre-rendered but doesn't impact performance */}
<Activity mode={currentTab === 'profile' ? 'visible' : 'hidden'}>
<ProfileTab />
</Activity>
{/* Hidden tab - state preserved even when not visible */}
<Activity mode={currentTab === 'settings' ? 'visible' : 'hidden'}>
<SettingsTab />
</Activity>
</div>
);
}Activity Modes
Available options:
| Mode | Behavior |
|---|---|
visible |
Renders normally, visible to user |
hidden |
Pre-rendered but hidden, state preserved |
Performance Benefits
Navigation comparison:
// BEFORE: Without Activity - re-render on every navigation
function OldApp() {
const [tab, setTab] = useState('home');
// Each tab change destroys and recreates the component
return (
<div>
{tab === 'home' && <HomeTab />}
{tab === 'profile' && <ProfileTab />}
</div>
);
}
// AFTER: With Activity - state preserved
function NewApp() {
const [tab, setTab] = useState('home');
// Pre-rendered components, instant navigation
return (
<div>
<Activity mode={tab === 'home' ? 'visible' : 'hidden'}>
<HomeTab />
</Activity>
<Activity mode={tab === 'profile' ? 'visible' : 'hidden'}>
<ProfileTab />
</Activity>
</div>
);
}
Partial Pre-rendering (PPR)
What is PPR
Partial Pre-rendering allows pre-rendering static parts of the application and serving them from a CDN, filling in dynamic content afterwards.
The flow:
- Build time: Static parts pre-rendered
- CDN: Shell served instantly
- Runtime: Dynamic content fills the shell
- Result: Minimal time-to-first-byte
Practical Implementation
// page.jsx - Partial Pre-rendering
import { Suspense } from 'react';
// This part is pre-rendered at build
export default function ProductPage({ productId }) {
return (
<div>
{/* Static header - pre-rendered */}
<Header />
<Navigation />
{/* Dynamic content - loads after */}
<Suspense fallback={<ProductSkeleton />}>
<ProductDetails productId={productId} />
</Suspense>
{/* Dynamic reviews */}
<Suspense fallback={<ReviewsSkeleton />}>
<ProductReviews productId={productId} />
</Suspense>
{/* Static footer - pre-rendered */}
<Footer />
</div>
);
}Performance Gains
Observed metrics:
| Metric | Without PPR | With PPR | Improvement |
|---|---|---|---|
| TTFB | 800ms | 50ms | 94% |
| LCP | 2.5s | 1.2s | 52% |
| FCP | 1.8s | 0.3s | 83% |
Partial Pre-rendering combines the best of SSG and SSR: static speed with server dynamism.
useEffectEvent Hook
The Problem It Solves
Before useEffectEvent, dealing with callbacks in effects was problematic:
// BEFORE: Problem with dependencies
function ChatRoom({ roomId, onMessage }) {
useEffect(() => {
const connection = createConnection(roomId);
connection.on('message', (msg) => {
// onMessage changes every render, causing reconnection
onMessage(msg);
});
return () => connection.close();
}, [roomId, onMessage]); // onMessage as dependency = problem
}The Solution With useEffectEvent
// AFTER: useEffectEvent solves it
import { useEffectEvent } from 'react';
function ChatRoom({ roomId, onMessage }) {
// Creates a stable event handler
const handleMessage = useEffectEvent((msg) => {
onMessage(msg);
});
useEffect(() => {
const connection = createConnection(roomId);
connection.on('message', handleMessage);
return () => connection.close();
}, [roomId]); // onMessage doesn't need to be a dependency!
}When to Use
Ideal use cases:
- Event handlers in effects: Callbacks that shouldn't re-trigger the effect
- Analytics: Tracking that needs current values
- Notifications: Handlers that change but shouldn't reconnect
- WebSockets: Stable message callbacks
// Example: Analytics without re-trigger
function ProductPage({ product, analytics }) {
const trackView = useEffectEvent(() => {
// Always uses the latest analytics
analytics.track('product_view', { id: product.id });
});
useEffect(() => {
trackView();
}, [product.id]); // analytics is not a dependency
}
cacheSignal API
What is cacheSignal
The cacheSignal API allows creating cache signals to invalidate data reactively.
import { cacheSignal, use } from 'react';
// Creates a cache signal for products
const productCache = cacheSignal();
async function fetchProduct(id) {
const response = await fetch(`/api/products/${id}`);
return response.json();
}
function ProductDetails({ productId }) {
// use() with cache signal
const product = use(fetchProduct(productId), {
signal: productCache
});
return (
<div>
<h1>{product.name}</h1>
<p>{product.description}</p>
<button onClick={() => productCache.invalidate()}>
Reload
</button>
</div>
);
}Granular Invalidation
// Invalidate specific cache
productCache.invalidate(); // Invalidates all products
// Invalidate by key
productCache.invalidate(productId); // Invalidates specific product
// Invalidate with condition
productCache.invalidateIf((key) => key.startsWith('featured-'));
Performance Tracks
Performance Debugging
React 19.2 introduced Performance Tracks for advanced performance debugging.
What it shows:
- Render timing: How long each component takes
- Suspense boundaries: When and why they suspend
- Transitions: Duration and states
- Effects: Timing of effects and cleanup
How to Use
// Enable Performance Tracks in dev
import { enableProfiler } from 'react-devtools';
enableProfiler({
tracks: true,
showSuspenseFallbacks: true,
highlightUpdates: true
});DevTools Integration
React DevTools 5.x:
- Tracks visualization in Profiler
- Suspense timeline
- Improved flamegraph
- Re-render metrics
Migrating to React 19.2
Step by Step
1. Update dependencies:
npm install react@19.2.0 react-dom@19.2.02. Check compatibility:
// React 19.2 requires:
// - Node.js 18+
// - Modern bundler (Vite, webpack 5+)
// - TypeScript 5.0+ (if using TS)3. Adopt features gradually:
// Start with Activity in navigation areas
<Activity mode={isActive ? 'visible' : 'hidden'}>
<ExpensiveComponent />
</Activity>
// Then add PPR to static pages
// Finally, migrate effects to useEffectEventCommon Issues
Frequent errors:
| Error | Cause | Solution |
|---|---|---|
| Activity not defined | Missing import | import { Activity } from 'react' |
| Hydration mismatch | Server/Client diff | Check diff logs |
| useEffectEvent in non-effect | Incorrect usage | Use only for effects |
React 19 vs Alternatives
Framework Comparison
React 19.2 vs others:
| Feature | React 19.2 | Svelte 5 | Vue 3.5 | Solid 2 |
|---|---|---|---|---|
| Server Components | Yes | Partial | Nuxt | No |
| PPR | Yes | No | No | No |
| Activity-like | Yes | No | KeepAlive | No |
| Signals | Via hooks | Native | Native | Native |
When to Choose React 19
React 19.2 is ideal for:
- Enterprise applications with SSR
- Complex dashboards with many tabs
- E-commerce with PPR
- Apps that need preserved state
The Future of React
2026 Roadmap
What to expect:
- React Compiler GA: Automatic memoization optimization
- More cache APIs: Even more granular invalidation
- Improved Activity: More modes and options
- AI DevTools: Optimization suggestions
Market Impact
Trends:
- React maintains leader position
- Next.js as main framework
- Server Components as standard
- Performance as differentiator
Conclusion
React 19.2 represents the maturity of React's async-first vision. Activity component and Partial Pre-rendering solve real UX problems that developers have faced for years.
If you haven't migrated to React 19 yet, now is the time. The performance and DX features justify the upgrade effort.
To understand more about the JavaScript ecosystem in 2025, I recommend checking out the article about TypeScript Surpasses Python where we analyze the most used languages.
Let's go! 🦅
💻 Master JavaScript To Master React
React is built on JavaScript. Mastering the fundamentals is essential to take full advantage of new features.
Complete Material
I've prepared a guide from basics to advanced:
Investment:
- 1x of $4.90 on card
- or $4.90 at sight

