⚛️

React 19

The biggest React release in years — Actions, new hooks, and a smarter runtime

React 19 (stable since December 5, 2024) ships a complete rethinking of how mutations and async work is handled. The release introduces Actions, four brand-new hooks, the use API, fully-stable Server Components, and a raft of ergonomic improvements that reduce boilerplate across the board.

ActionsuseActionStateuseFormStatususeOptimisticuse APIref as prop<Context> providerDocument MetadataServer ActionsResource Preloading

Actions ⚡

The new primitive for async mutations — pending state, errors, and optimistic updates handled automatically.

Before React 19 you had to wire up isPending, error, and multiple useState calls every time you submitted data. React 19 introduces Actions: async functions passed to transitions (or to <form action={}>) that React manages for you.

❌ Before — manual plumbing

function UpdateName() {
  const [name, setName] = useState("");
  const [error, setError] = useState(null);
  const [isPending, setIsPending] =
    useState(false);

  const handleSubmit = async () => {
    setIsPending(true);
    const error = await updateName(name);
    setIsPending(false);
    if (error) {
      setError(error);
      return;
    }
    redirect("/path");
  };

  return (
    <div>
      <input
        value={name}
        onChange={e => setName(e.target.value)}
      />
      <button
        onClick={handleSubmit}
        disabled={isPending}
      >
        Update
      </button>
      {error && <p>{error}</p>}
    </div>
  );
}

✅ After — useTransition with async Action

function UpdateName() {
  const [name, setName] = useState("");
  const [error, setError] = useState(null);
  const [isPending, startTransition] =
    useTransition();

  const handleSubmit = () => {
    startTransition(async () => {
      const error = await updateName(name);
      if (error) {
        setError(error);
        return;
      }
      redirect("/path");
    });
  };

  return (
    <div>
      <input
        value={name}
        onChange={e => setName(e.target.value)}
      />
      <button
        onClick={handleSubmit}
        disabled={isPending}
      >
        Update
      </button>
      {error && <p>{error}</p>}
    </div>
  );
}

🎁 What Actions give you automatically

Pending state

isPending is true from start to final commit — zero manual setters

Optimistic updates

Via useOptimistic — instant UI feedback while the server responds

🚨

Error handling

Errors propagate to Error Boundaries and revert optimistic state

📄

Form reset

<form action={fn}> resets uncontrolled inputs after success

useActionState 🪝

The one-stop hook that wraps your Action and hands back state, a callable Action, and a pending flag.

useActionState accepts an async Action function and an initial state. It returns [state, dispatchAction, isPending]. Every time you call dispatchAction, React invokes your function, tracks pending, and stores whatever you return as the new state.

useActionState pattern

import { useActionState } from "react";

async function updateNameAction(prevState, formData) {
  const name = formData.get("name");
  const error = await updateName(name);
  if (error) return error;      // stored as state
  redirect("/profile");
  return null;
}

function RenameForm() {
  const [error, submitAction, isPending] =
    useActionState(updateNameAction, null);

  return (
    <form action={submitAction}>
      <input type="text" name="name" />
      <button type="submit" disabled={isPending}>
        {isPending ? "Saving…" : "Save"}
      </button>
      {error && <p className="text-red-500">{error}</p>}
    </form>
  );
}
📝 Note: useActionState was previously called ReactDOM.useFormState in Canary. It has been renamed and useFormState is now deprecated.

useActionState — Live Demo

This form uses useActionState to manage async mutation state. React auto-tracks isPending, the previous state, and the latest result — no useState required.

isPending: false

state.status: idle

useFormStatus 📋

Read the parent form's submission status from any child component — no prop drilling needed.

Design-system components (buttons, spinners, inputs) often need to know whether their surrounding form is submitting. With useFormStatus they can read that state independently, like a Context consumer — but without any setup.

useFormStatus in a design-system button

import { useFormStatus } from "react-dom";

// This component lives deep inside the form
function SubmitButton() {
  const { pending, data, method, action } =
    useFormStatus();

  return (
    <button type="submit" disabled={pending}>
      {pending ? "Submitting…" : "Submit"}
    </button>
  );
}

// Usage — SubmitButton reads form status automatically
function CheckoutForm() {
  return (
    <form action={checkoutAction}>
      <input name="card" />
      <SubmitButton />   {/* no props needed */}
    </form>
  );
}
pendingboolean

true while the form action is executing

dataFormData | null

the form data being submitted

method"get" | "post"

HTTP method of the form

📋

useFormStatus — Deep-Nested Fields

useFormStatus lets child components — even buried deep in a component tree — subscribe to their ancestor form's pending state without prop-drilling.

Key insight:

Neither FormInput nor SubmitButton receive any props about pending state — they call useFormStatus() themselves, reading the nearest parent form's state.

useOptimistic 🚀

Instantly reflect a UI change while the server is still processing — then reconcile when done.

useOptimistic takes a piece of state and returns an optimistic copy of it. You call addOptimistic inside an Action to immediately show the expected result. React automatically rolls back to the real state once the async operation settles.

Optimistic todo list

import { useOptimistic } from "react";

function TodoList({ todos, addTodo }) {
  // optimisticTodos mirrors todos PLUS any pending add
  const [optimisticTodos, addOptimistic] =
    useOptimistic(todos);

  const formAction = async (formData) => {
    const text = formData.get("todo");

    // ✅ Paint the new item immediately
    addOptimistic([...optimisticTodos, { text, pending: true }]);

    // Server round-trip (may take 1-2s)
    await addTodo(text);
    // React automatically switches to the real server state
  };

  return (
    <>
      <ul>
        {optimisticTodos.map((todo, i) => (
          <li key={i} style={{ opacity: todo.pending ? 0.5 : 1 }}>
            {todo.text}
          </li>
        ))}
      </ul>
      <form action={formAction}>
        <input name="todo" />
        <button type="submit">Add</button>
      </form>
    </>
  );
}
✅ Key behaviour: If the async action succeeds, React replaces the optimistic value with the real state. If it fails, React automatically reverts to the value before the optimistic update — no manual cleanup needed.

useOptimistic — Live Demo

Messages appear instantly in a "pending" state while the server roundtrip completes. On failure, React auto-reverts to the last confirmed state.

Hey everyone! 👋
What's new in React 19?
useOptimistic is amazing!

Each message simulates a 1.5s network delay. Notice the instant optimistic paint, then the "confirmed" swap.

The use API 🎣

Read a Promise or a Context value at any point during render — even inside if blocks and loops.

use is a new React API (not a hook) that can be called conditionally, unlike all existing hooks. When passed a Promise it integrates with Suspense — suspending the component until the promise resolves. When passed a Context it reads the nearest provider value.

📦 Streaming data from Server → Client

// server component (app/page.tsx)
import { Message } from "./message";

export default function Page() {
  // Promise passed as prop — NOT awaited here
  const msgPromise = fetchMessage();
  return (
    <Suspense fallback={<p>Loading…</p>}>
      <Message messagePromise={msgPromise} />
    </Suspense>
  );
}

// client component (message.tsx)
"use client";
import { use } from "react";

export function Message({ messagePromise }) {
  // 🛑 suspends until resolved
  const msg = use(messagePromise);
  return <p>{msg}</p>;
}

🎨 Conditional Context reading

import { use } from "react";
import { ThemeContext } from "./theme";

function Heading({ children }) {
  // ✅ use() can be called after an early return
  if (!children) return null;

  // useContext() would crash here due to hook rules
  const theme = use(ThemeContext);

  return (
    <h1 style={{ color: theme.color }}>
      {children}
    </h1>
  );
}
⚠️ Gotcha: Do NOT create a Promise inside a Client Component render and pass it straight to use(). Every render creates a new Promise, which causes an infinite loop. Always create Promises in Server Components or stable top-level caches and pass them down as props.

Server Components & Server Actions 🖥️

Stable in React 19 — zero-bundle-size components and secure server-side mutations.

🧩 Server Components (stable)

Server Components render entirely on the server (or at build time) and send plain HTML or the RSC payload to the client. They can await any async data source directly — no useEffect, no client-side fetch.

app/products/page.tsx — Server Component by default

export default async function ProductsPage() {
  // Fetch runs on the server — never ships to the browser
  const products = await db.product.findMany();

  return (
    <ul>
      {products.map(p => (
        <li key={p.id}>{p.name} — {p.price}</li>
      ))}
    </ul>
  );
}
⚠️ Common misunderstanding: There is no "use server" directive for Server Components. That directive marks Server Actions only.
🔁 Server Actions ("use server")

Server Actions let Client Components call async functions that execute exclusively on the server. The framework automatically creates a reference, serialises arguments, and routes the RPC call.

Server Action + Client Component

// actions.ts
"use server";

export async function deletePost(id: string) {
  await db.post.delete({ where: { id } });
  revalidatePath("/posts");
}

// PostCard.tsx  — Client Component
"use client";
import { deletePost } from "./actions";

export function PostCard({ post }) {
  return (
    <div>
      <h2>{post.title}</h2>
      {/* Calling deletePost hits the server, never the client bundle */}
      <button onClick={() => deletePost(post.id)}>
        Delete
      </button>
    </div>
  );
}

Ergonomic Improvements 🛠️

Quality-of-life upgrades that reduce boilerplate throughout your component tree.

forwardRef → goneref as a prop

Function components now receive ref as a plain prop. No more wrapping in forwardRef.

❌ React 18

const MyInput = forwardRef(
  function MyInput({ placeholder }, ref) {
    return (
      <input ref={ref} placeholder={placeholder} />
    );
  }
);

✅ React 19

function MyInput({ placeholder, ref }) {
  return (
    <input ref={ref} placeholder={placeholder} />
  );
}
<Context.Provider> → deprecated<Context> as a provider

❌ React 18

<ThemeContext.Provider value="dark">
  {children}
</ThemeContext.Provider>

✅ React 19

<ThemeContext value="dark">
  {children}
</ThemeContext>
🧹 Cleanup functions for refs

Ref callbacks can now return a cleanup function. React calls it when the element is removed from the DOM — just like an effect cleanup.

Ref callback with cleanup

<input
  ref={(el) => {
    // el is assigned → setup
    const observer = new ResizeObserver(handleResize);
    observer.observe(el);

    // React calls this when the element unmounts
    return () => observer.disconnect();
  }}
/>
useDeferredValue — initial value

You can now pass a second argument to useDeferredValue as the value to use on the very first render, before the deferred re-render fires.

Initial value prevents flicker on first render

function Search({ query }) {
  // First render: value = "" (blank, no flicker)
  // Subsequent: value updates lazily in background
  const deferredQuery = useDeferredValue(query, "");
  return <Results query={deferredQuery} />;
}

Native Document Metadata 📄

Render <title>, <meta>, and <link> from anywhere in the tree — React hoists them to <head> automatically.

Previously you needed react-helmet or framework-specific APIs. In React 19 these tags work natively with Client rendering, Streaming SSR, and Server Components.

Metadata co-located with component

function BlogPost({ post }) {
  return (
    <article>
      {/* React hoists these three into <head> */}
      <title>{post.title}</title>
      <meta name="author" content={post.author} />
      <link rel="canonical" href={post.url} />

      <h1>{post.title}</h1>
      <p>{post.body}</p>
    </article>
  );
}

Smart Stylesheet & Script Loading 🎨

Co-locate stylesheets and async scripts with the components that need them — React deduplicates and orders them automatically.

📦 Stylesheets with precedence

Pass a precedence prop to a <link rel='stylesheet'> and React handles insertion order, deduplication, and Suspense integration (the page won't reveal until the sheet is loaded).

Stylesheet precedence controls load order

function Dashboard() {
  return (
    <Suspense fallback="Loading…">
      {/* precedence controls insertion order */}
      <link rel="stylesheet" href="/base.css"  precedence="low"  />
      <link rel="stylesheet" href="/theme.css" precedence="high" />
      <main>…</main>
    </Suspense>
  );
}
⚡ Async scripts — deduplication

Render <script async src='…' /> anywhere in the tree. React loads each unique src only once, regardless of how many components render it.

Same script, two components — one load

// Both components render the same widget script,
// but the browser loads it only once.
function MapWidget() {
  return (
    <>
      <script async src="https://example.com/map.js" />
      <div id="map" />
    </>
  );
}

function App() {
  return (
    <>
      <MapWidget />
      <MapWidget /> {/* no duplicate script tag */}
    </>
  );
}
🚀 Resource Preloading APIs

New imperative APIs from react-dom let you hint the browser about upcoming resources early — before any component even renders.

Browser resource hints from React

import {
  prefetchDNS,  // resolve DNS for a host early
  preconnect,   // open a TCP connection early
  preload,      // fetch font / style / script early
  preinit,      // fetch AND execute a script early
} from "react-dom";

function App() {
  prefetchDNS("https://fonts.googleapis.com");
  preconnect("https://cdn.example.com");
  preload("https://cdn.example.com/hero.jpg", { as: "image" });
  preinit("https://cdn.example.com/analytics.js", { as: "script" });
  // …
}

Better Error Reporting & Hydration 🔍

Fewer duplicate logs, richer diffs, and new lifecycle callbacks for error boundaries.

💧 Hydration error diffs

React 18 fired multiple overlapping warnings for a single hydration mismatch. React 19 collapses them into one structured message with a unified diff:

React 19 console output — single structured message

Uncaught Error: Hydration failed because the server rendered HTML
didn't match the client.

  <App>
    <span>
+     Client        ← client sent this
-     Server        ← server sent this

  at throwOnHydrationMismatch …
🚨 New root-level error callbacks

createRoot and hydrateRoot now accept three callbacks so you can route errors to an error-tracking service with full context.

Three error lifecycle callbacks

createRoot(document.getElementById("root"), {
  onCaughtError(error, errorInfo) {
    // Error caught by an Error Boundary
    Sentry.captureException(error, errorInfo);
  },
  onUncaughtError(error, errorInfo) {
    // Error NOT caught — app will likely crash
    Sentry.captureException(error, errorInfo);
  },
  onRecoverableError(error, errorInfo) {
    // React self-recovered (hydration mismatch, etc.)
    console.warn("Recoverable:", error);
  },
}).render(<App />);

Custom Elements — Full Support 🌐

React 19 passes all tests on Custom Elements Everywhere — properties vs attributes are now handled correctly on both client and server.

Client-side rendering

Props matching a property on the Custom Element instance are set as properties. All others become attributes.

Server-side rendering

Primitive props (string, number, true) render as attributes. Objects, functions, and false are omitted.

Custom element props in React 19 ✅

<my-video-player
  src="/video.mp4"       // → attribute (string)
  volume={0.8}           // → property on client, attribute on server
  onPlaybackEnd={fn}     // → property (function, SSR-omitted)
  autoplay               // → attribute (boolean true)
/>

New React DOM Static APIs 📦

prerender and prerenderToNodeStream — static-site generation that actually waits for all data.

The existing renderToString didn't wait for suspended data. The new static APIs do — making them ideal for SSG build steps.

prerender — waits for ALL suspended data

import { prerender } from "react-dom/static";

export async function GET() {
  // Waits for ALL suspended data before resolving
  const { prelude } = await prerender(<App />, {
    bootstrapScripts: ["/main.js"],
  });

  return new Response(prelude, {
    headers: { "content-type": "text/html" },
  });
}

Quick Reference 📚

Everything new at a glance.

New concept

Actions

Async functions in transitions. React manages pending, errors, and form reset.

🪝New hook

useActionState

Wrap an Action and get [state, dispatch, isPending] back.

📋New hook

useFormStatus

Read parent <form> pending/data from any child — no prop passing.

🚀New hook

useOptimistic

Instantly reflect a mutation in the UI; auto-revert on failure.

🎣New API

use()

Read a Promise or Context conditionally — even after early returns.

🖥️Stable

Server Actions

"use server" functions called from Client Components via RPC.

🔗Improvement

ref as prop

Pass ref directly — forwardRef is no longer needed.

🎨Improvement

<Context> provider

Use <MyCtx value={…}> instead of <MyCtx.Provider>.

📄Improvement

Document Metadata

<title>, <meta>, <link> are hoisted to <head> automatically.

🧹Improvement

Ref cleanup

Ref callbacks can return a cleanup function, like useEffect.

🎭Improvement

Stylesheet precedence

Manage CSS insertion order and defer renders until loaded.

🌐Improvement

Custom Elements

Full property/attribute support on client and server.

React in 2025 — What's New 📅

A running log of every significant release, feature drop, and ecosystem event from February 2025 through February 2026.

Feb 2025

Create React App officially deprecated

ecosystem

After nearly a decade CRA is deprecated. The React team recommends Next.js, React Router, or Expo for new projects. CRA enters maintenance mode.

Mar 2025

React 19.1 released

release

Patch release with stability improvements, updated useId prefix from :r: to «r», and critical bug fixes across the runtime and SSR.

Apr 2025

React Labs: View Transitions & Activity

experimental

Two new experimental features ready to test: <ViewTransition> for declarative page animations and <Activity> for state-preserving visibility control. Also previewed: Performance Tracks, Compiler IDE Extension, Automatic Effect Dependencies.

Oct 1, 2025

React 19.2 released

release

Ships stable <Activity>, useEffectEvent, cacheSignal, Partial Pre-rendering, Performance Tracks for Chrome DevTools, SSR batching improvements, Web Streams for Node.js, and eslint-plugin-react-hooks v6.

Oct 7, 2025

React Compiler v1.0 stable

tooling

First stable release of React Compiler with automatic memoization, compiler-powered lint rules, SWC plugin support, and default enablement in new Expo/Vite/Next.js projects.

Oct 7, 2025

React Foundation announced

governance

Independent non-profit to steward React's long-term open source development and community health. Meta contributed the React trademark and brand assets.

Oct 16, 2025

React Conf 2025

event

Henderson, NV. Keynotes covered React 19.2 features, React Compiler v1.0, React Native 0.82 (New Architecture only), Fragment Refs, Partial Pre-rendering, and Async React patterns.

Dec 3, 2025

Critical RSC Security Vulnerability — CVE-2025-55182

security

CVSS 10.0 unauthenticated RCE via malicious deserialization of Server Function payloads. Fixed in v19.0.1, v19.1.2, v19.2.1. Update all react-server-dom-* packages immediately.

Dec 11, 2025

Follow-up RSC vulnerabilities patched

security

Two additional CVEs: Denial of Service (CVE-2025-55184, CVSS 7.5) and Source Code Exposure (CVE-2025-55183, CVSS 5.3). All fixed in the same v19.x.1+ patch releases.

Jan 2026

CVE-2026-23864 — DoS follow-up

security

Additional Denial of Service vulnerability (CVSS 7.5) patched in January 2026. Keep react-server-dom-* packages on latest.

Sunsetting Create React App 🌇

February 14, 2025 — React team officially deprecates the tool that bootstrapped an entire generation of apps.

After nearly a decade, Create React App (CRA) is deprecated as of February 2025. The React team cited no active maintainers and the fact that modern production apps require routing, code splitting, data-fetching coordination, and SSR — all solved more elegantly by frameworks.

🚀

New apps

Use Next.js, React Router (framework mode), or Expo

🔧

Custom setups

Roll your own with Vite, Parcel, or Rsbuild

📦

Migration guides

Vite, Next.js, React Router, and Expo migration docs published

📝 CRA in maintenance mode: Existing CRA apps continue to work and a final version was published for React 19 compatibility. No new features will be added. A deprecation warning now appears on npx create-react-app.

React 19.1 March 2025

The first patch release in the React 19 series — improved stability and a useId hash update.

React 19.1 focused on hardening the 19.0 features and fixing regressions reported in the wild. The most visible change is a new default prefix for useId-generated IDs, paving the way for View Transition compatibility.

🆔

useId prefix updated

Default prefix changed from :r: (19.0) to «r» (19.1) to avoid conflicts with View Transition name requirements.

🐛

Bug-fix release

Dozens of fixes across the runtime, SSR streaming, and hydration edge cases reported after the 19.0 stable launch.

🔒

Security patch baseline

v19.1.0 and v19.1.1 were later found to contain the RSC RCE vulnerability; v19.1.2 is the safe version.

📦

Upgrade path

Drop-in upgrade: npm install react@19.1 react-dom@19.1 — no API changes required.

View Transitions & Activity Experimental / Canary

April 23, 2025 Labs post — two features ready to try today.

<ViewTransition>

A new component that wraps elements you want animated. Three primitives control everything:

  • What <ViewTransition> wraps the element
  • When startTransition, useDeferredValue, or <Suspense>
  • How — CSS ::view-transition-* pseudo-elements

ViewTransition usage

import { ViewTransition } from 'react';

// Navigate → cross-fade automatically
<ViewTransition key={url}>
  {url === '/' ? <Home /> : <Details />}
</ViewTransition>

// Shared element between pages
<ViewTransition name={`video-${id}`}>
  <Thumbnail />
</ViewTransition>
🫧

<Activity>

Hide and show parts of the UI while preserving state. Available stable in React 19.2.

  • mode="visible" — renders normally, effects mount
  • mode="hidden" — unmounts effects, defers updates, saves state

Activity usage

import { Activity } from 'react';

// Before: state lost on unmount
{isVisible && <Page />}

// After: state preserved when hidden
<Activity mode={isVisible ? 'visible' : 'hidden'}>
  <Page />
</Activity>

// Pre-render likely next route in background
<Activity mode={url === '/about' ? 'visible' : 'hidden'}>
  <About />
</Activity>

Also in development (from the same Labs post)

React Performance Tracks

Custom Chrome DevTools lanes — shipped in 19.2

Compiler IDE Extension

LSP-powered hints, CodeLens for effect deps

Automatic Effect Dependencies

Compiler infers & inserts deps — no array typing

Fragment Refs

ref on <> pointing to multiple DOM nodes

Gesture Animations

Swipe / continuous gesture view transitions

Concurrent Stores

use(store) replacing useSyncExternalStore

<ViewTransition> Patterns

canary

Three concepts control View Transitions: "what" (wrap with <ViewTransition>), "when" (startTransition, useDeferredValue, or <Suspense>), and "how" (CSS ::view-transition-* pseudo-selectors).

Wrap route content in <ViewTransition> to get a default cross-fade on navigation.

import { ViewTransition } from 'react';

// Wrap the page that changes on navigation
export default function App() {
  const { url } = useRouter();

  return (
    // "WHAT" to animate
    <ViewTransition key={url}>
      {url === '/' ? <Home /> : <Details />}
    </ViewTransition>
    // "WHEN": startTransition triggers the animation
  );
}
startTransition

Navigation, state toggle

useDeferredValue

Live search, list filter

<Suspense>

Fallback → content swap

Available in react@canary. Next.js 16 Canary supports ViewTransition via @vitejs/plugin-rsc or the canary channel.

React 19.2 October 1, 2025

The headline release of 2025: stable Activity, useEffectEvent, Performance Tracks, Partial Pre-rendering, and more.

New React Features

🎯useEffectEvent

Separates "event-like" logic from synchronization logic inside Effects. An Effect Event always sees the latest props and state without being listed as a dependency — preventing unwanted Effect re-runs.

useEffectEvent — stable dep array without stale values

import { useEffect, useEffectEvent } from 'react';

function ChatRoom({ roomId, theme }) {
  // ✅ onConnected always sees latest theme
  //    but does NOT go in the dependency array
  const onConnected = useEffectEvent(() => {
    showNotification('Connected!', theme);
  });

  useEffect(() => {
    const connection = createConnection(roomId);
    connection.on('connected', () => onConnected());
    connection.connect();
    return () => connection.disconnect();
  }, [roomId]); // ← only roomId, NOT theme
}

🫧<Activity> — now stable

Officially stable in React 19.2 with two modes: mode="visible" (normal render, effects mount) and mode="hidden" (effects unmounted, updates deferred, state preserved). SSR optimization: hidden Activity is excluded from the initial HTML response and rendered client-side at low priority.

🔔cacheSignal (Server Components only)

Lets you know when a cache() entry lifetime is over so you can abort in-flight requests. Useful for passing to fetch(url, { signal: cacheSignal() } ) inside cached Server Component functions.

cacheSignal — abort stale cache requests

import { cache, cacheSignal } from 'react';

const fetchUser = cache(async (id) => {
  // Abort the request if the cache result is no longer used
  return fetch(`/api/users/${id}`, { signal: cacheSignal() });
});

📊React Performance Tracks

React 19.2 adds custom tracks to Chrome DevTools Performance profiles. Two tracks are exposed:

Scheduler track

Shows work priorities (blocking, transition), events that triggered updates, and when renders happened. Visualizes how React splits work across priorities.

Components track

Shows the component tree being rendered or running effects. Labels include Mount, Blocked, and Update to reveal time spent on each component.

🎯

useEffectEvent — React 19.2

useEffectEvent separates "event-like" code from synchronization code. The notification always shows the current theme, yet changing the theme does not reconnect to the room.

Connecting to #general…

No events yet…

Try it: Switching the theme updates the next connection message — but look at the log, it doesn't reconnect. Only changing the room causes a reconnect.
🫧

<Activity> — React 19.2

Switch between channels. With <Activity>, each panel's state (chat history, input text) is preserved when hidden — even though the component is unmounted. Navigate back and everything is still there.

#general Channel

💬 Welcome to the blue channel!

How to test: Type in a channel, switch away, come back — your typed text is still there. Without <Activity> (conditional rendering only), state would be lost.
New React DOM Features

Partial Pre-rendering (PPR)

Pre-render the static shell of an app ahead of time (serve from CDN), then resume rendering dynamic content into it later. Uses a two-step API:prerender() with an AbortController, then resume() / resumeAndPrerender().

PPR — static shell + dynamic resume

// Step 1 — Pre-render static shell
const { prelude, postponed } = await prerender(<App />, {
  signal: controller.signal,
});
await savePostponedState(postponed);

// Step 2 — Resume with dynamic data (server-side)
const stream = await resume(<App />, await getPostponedState(req));

// Or for SSG: resume back to static HTML
const { prelude } = await resumeAndPrerender(<App />, postponedState);
Notable Changes in 19.2
🔄

Batching SSR Suspense reveals

Suspense boundaries now batch content reveals during streaming SSR for a short time, aligning with client-render behavior and enabling smoother ViewTransition animations.

🌊

Web Streams for Node.js

renderToReadableStream and prerender now available for Node.js. New resume / resumeAndPrerender APIs for Web Streams. (Still recommend Node Streams for performance.)

🔧

eslint-plugin-react-hooks v6

Flat config by default in the recommended preset. Opt-in compiler-powered rules. Legacy config users: switch to recommended-legacy.

🆔

useId prefix updated to _r_

Changed from «r» (19.1) to _r_ so generated IDs are valid for view-transition-name and XML 1.0 names.

React Compiler v1.0 🤖 October 7, 2025

A build-time tool that automatically memoizes your React components and hooks — already installed in this project.

✅ Already active here: babel-plugin-react-compiler@^1.0.0 is installed in this project (see package.json). Every component and hook in this app is already automatically memoized.

❌ Before — manual memoization

// You had to remember to memoize
const expensiveValue = useMemo(() => {
  return computeExpensiveValue(a, b);
}, [a, b]);

const handleClick = useCallback(() => {
  doSomething(a, b);
}, [a, b]);

const MemoChild = React.memo(({ data }) => (
  <div>{data}</div>
));

✅ After — compiler handles it

// Write plain code. Compiler memoizes
// exactly what needs memoizing — and
// can memoize after early returns which
// useMemo/useCallback cannot do.

const expensiveValue = computeExpensiveValue(a, b);

const handleClick = () => {
  doSomething(a, b);
};

// No React.memo wrapper needed
function Child({ data }) {
  return <div>{data}</div>;
}

12% faster loads

Meta Quest Store saw up to 12% improvement in initial load and cross-page navigations, and 2.5× faster interactions.

🔍 Compiler-powered linting

New lint rules in eslint-plugin-react-hooks ship with the compiler: set-state-in-render, set-state-in-effect, refs safety checks.

🔄 Backwards compatible

Works with React 17+. Add react-compiler-runtime as a dependency and set a target in compiler config for pre-19 projects.

🏗️ Default in new projects

Partnered with Expo (SDK 54+), Vite, and Next.js — enabled by default in create-expo-app, create-vite, create-next-app.

⚙️ SWC support (experimental)

Experimental SWC plugin dramatically speeds up Next.js builds when React Compiler is enabled. Requires Next.js 15.3.1+.

🔒 Pin exact version

If test coverage is limited, pin babel-plugin-react-compiler to an exact version (1.0.0) rather than a semver range to avoid unexpected memoization changes.

📝 What to do with existing useMemo/useCallback?
  • Leave existing memoization in place for now — removing it can change compilation output.
  • For new code, omit manual memoization and let the compiler handle it.
  • Continue using useMemo / useCallback as an escape hatch when you need precise control (e.g., as effect dependencies).

The React Foundation 🏛️ October 7, 2025

An independent non-profit to steward React's long-term open source development beyond Meta.

Announced during React Conf 2025, the React Foundationis a new governance structure that separates React's open source stewardship from Meta. Meta contributed the React trademark and brand assets to the Foundation.

🔓

Open source stewardship

Independent governance ensures React's development continues to serve the wider community, not just Meta's internal needs.

🤝

Trademark & brand

Meta has contributed the React trademark and brand assets to the Foundation, formalizing community ownership.

🌍

Community health

The Foundation's mission includes long-term community sustainability, contributor support, and ecosystem coordination.

Security Advisories 🔒 Dec 2025 – Jan 2026

A series of vulnerabilities were disclosed and patched in react-server-dom-* packages. If you use RSC, update now.

🚨

CVE-2025-55182 — CVSS 10.0 (Critical)

Unauthenticated Remote Code Execution via React Server Functions. Disclosed December 3, 2025.

An unauthenticated attacker could craft a malicious HTTP request to any Server Function endpoint. When deserialized by React, it achieves remote code execution on the server. Apps that use React Server Components or Server Functions are affected, even if they don't define explicit endpoints. Reported by Lachlan Davidson via Meta Bug Bounty.

Affected packages

react-server-dom-webpack, react-server-dom-parcel, react-server-dom-turbopack

Affected versions

19.0.0, 19.1.0, 19.1.1, 19.2.0

Fixed in

v19.0.1, v19.1.2, v19.2.1+

CVE-2025-55184

High (CVSS 7.5)

Denial of Service

Dec 11, 2025

Fix: Same v19.x.1+ patch releases

CVE-2025-55183

Medium (CVSS 5.3)

Source Code Exposure

Dec 11, 2025

Fix: Same v19.x.1+ patch releases

CVE-2026-23864

High (CVSS 7.5)

Denial of Service

Jan 26, 2026

Fix: Latest react-server-dom-* packages

Affected frameworks — update commands

Next.js

npm install next@latest

See nextjs.org/blog for your release line

React Router (RSC)

npm install react@latest react-dom@latest react-server-dom-webpack@latest

Only if using unstable RSC APIs

Waku

npm install waku@latest react@latest react-dom@latest react-server-dom-webpack@latest

Expo

See expo.dev/changelog

Mitigation guide published

Not using RSC / Server Functions? Apps that do not use React Server Components, Server Functions, or any of the affected server-dom-* packages are not affected.

React 19.2 — Latest stable

Released October 1, 2025 · React Compiler v1.0 is installed in this project

Install latest

npm install react@latest react-dom@latest

Add Compiler

npm install -D --save-exact babel-plugin-react-compiler@latest

Try canary

npm install react@canary react-dom@canary

See the Upgrade Guide · React 19.2 Blog Post · React Compiler Docs