The React team published React 19.2 to npm on October 1, 2025. It is the third release in a year, after React 19 in December and 19.1 in June. Two APIs give names to patterns you were already faking: <Activity> hides UI and keeps its state, and useEffectEvent lets an effect read fresh values without re-running. The release also adds Chrome DevTools tracks, partial pre-rendering and a change to Suspense streaming that affects your LCP.
Activity: hide a tree, keep its state
<Activity> replaces the && pattern you use to show and hide parts of a page:
// Before: unmounting throws away state
{isVisible && <Page />}
// After: hidden keeps state and pauses the work
<Activity mode={isVisible ? "visible" : "hidden"}>
<Page />
</Activity>
In hidden mode React hides the children, unmounts their effects and defers their updates until it has nothing else to do. Switch back to visible and React remounts the effects with the old state intact.
You get three things out of it. A tab panel keeps its scroll position and half-typed form. A back navigation restores input fields. And you can render the page a user will likely open next while they read the current one, loading its data, CSS and images at low priority. The team says more modes will follow.
Hidden trees still cost memory, so wrap the screens people return to and leave the rest alone.
useEffectEvent ends the eslint-disable comment
The classic bug: a chat room effect that shows a notification with the current theme. Add theme to the dependencies and a theme change reconnects the socket. Leave it out and you disable the lint rule, then ship a stale value six months later.
function ChatRoom({ roomId, theme }) {
const onConnected = useEffectEvent(() => {
showNotification("Connected!", theme);
});
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.on("connected", () => onConnected());
connection.connect();
return () => connection.disconnect();
}, [roomId]); // onConnected stays out of the array
}
An Effect Event reads the latest props and state, like a DOM event handler. Keep it out of the dependency array, and declare it in the same component or Hook as its effect. The linter checks both. The team warns against using it to silence the linter. Use it for logic that the effect fires, such as analytics, notifications and logging, and keep values that should restart the effect as dependencies.
To get the rule right, update eslint-plugin-react-hooks to v6. Its recommended preset now uses flat config, and new React Compiler rules are opt-in. If you still use legacy config:
- extends: ["plugin:react-hooks/recommended"]
+ extends: ["plugin:react-hooks/recommended-legacy"]
Performance tracks in Chrome DevTools
React now writes its own tracks into Chrome performance profiles. The Scheduler track shows work by priority, blocking for interactions and transition for startTransition, plus the event that scheduled each update and the moment React waited for paint. The Components track shows which components rendered or ran effects, with labels like Mount and Blocked.
Before, you guessed from flame charts full of anonymous functions. Now you record a profile, find the slow interaction and see the component that caused it on the same timeline.
Partial pre-rendering
React DOM can now pre-render the static shell of a page, serve it from a CDN, and resume rendering the dynamic parts later:
const { prelude, postponed } = await prerender(<App />, {
signal: controller.signal,
});
await savePostponedState(postponed);
// Later, per request:
const stream = await resume(<App />, await getPostponedState(request));
resume and resumeAndPrerender cover Web Streams, and resumeToPipeableStream and resumeAndPrerenderToNodeStream cover Node streams. Frameworks will use these more than apps will. Next.js builds its own partial pre-rendering on the same idea.
Changes that alter behavior
| Change | What to check |
|---|---|
| SSR batches Suspense reveals for a short time, and stops batching near the 2.5 s LCP mark | Streaming pages reveal content in bigger chunks |
| useId prefix changes from «r» to r | Snapshot tests and CSS that match generated IDs |
| Web Streams APIs work in Node.js | Keep Node streams, which the team calls faster and which compress by default |
| Context stringifies as SomeContext, not SomeContext.Provider | Tests that assert on component names |
React changed the useId prefix so IDs work as view-transition-name values and XML names. If your snapshot tests store «r1», they fail on upgrade. The Suspense batching brings server streaming in line with client rendering and prepares for <ViewTransition>.
Server Components also gain cacheSignal(), an AbortSignal that fires when the cache() lifetime ends, so you can abort a fetch React no longer needs.
This week
- Upgrade
react,react-domandeslint-plugin-react-hookstogether. Re-run snapshot tests and update theuseIdprefixes. - Grep for
eslint-disable-next-line react-hooks/exhaustive-deps. Each hit is a candidate foruseEffectEvent. - Pick one tab or panel that loses state on switch and wrap it in
<Activity>. - Record a Chrome profile of your slowest interaction and read the Scheduler and Components tracks before you start optimizing.
Volodymyr Chornous

