Back to blog

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.0

TypeScript Type Compatibility

For TypeScript, make sure to update the types:

npm install @types/react@19.2.0 @types/react-dom@19.2.0

The 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.

Let's go! 🦅

Comments (0)

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

Add comments