Back to blog

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:

  1. Pre-render routes: Pages the user will likely access
  2. Preserve state: Maintain state of navigated parts
  3. Smooth transitions: Navigate without loading
  4. 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:

  1. Build time: Static parts pre-rendered
  2. CDN: Shell served instantly
  3. Runtime: Dynamic content fills the shell
  4. 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:

  1. Event handlers in effects: Callbacks that shouldn't re-trigger the effect
  2. Analytics: Tracking that needs current values
  3. Notifications: Handlers that change but shouldn't reconnect
  4. 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:

  1. Render timing: How long each component takes
  2. Suspense boundaries: When and why they suspend
  3. Transitions: Duration and states
  4. 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.0

2. 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 useEffectEvent

Common 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:

  1. React Compiler GA: Automatic memoization optimization
  2. More cache APIs: Even more granular invalidation
  3. Improved Activity: More modes and options
  4. 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

📚 Access JavaScript Guide

Comments (0)

This article has no comments yet 😢. Be the first! 🚀🦅

Add comments