React 19.2 (October 2025) adds two features that fix long-standing annoyances: <Activity> for preserving UI state, and useEffectEvent for effects that read fresh values.
Activity: hide without losing state
Conditional rendering unmounts a component and throws away its state. <Activity> has a hidden mode: the UI is hidden, its effects are unmounted and updates are deprioritized, but state (form inputs, scroll position) is kept.
import { Activity, useState } from 'react';
export function Tabs() {
const [tab, setTab] = useState<'draft' | 'preview'>('draft');
return (
<>
<button onClick={() => setTab('draft')}>Draft</button>
<button onClick={() => setTab('preview')}>Preview</button>
<Activity mode={tab === 'draft' ? 'visible' : 'hidden'}>
<Editor />
</Activity>
<Activity mode={tab === 'preview' ? 'visible' : 'hidden'}>
<Preview />
</Activity>
</>
);
}
Switching back to the draft keeps what the user typed. It is also useful for pre-rendering the next screen at low priority.
useEffectEvent: read the latest value without re-running
A common bug: an effect needs the latest theme for a notification, but adding it to the dependency array reconnects the chat.
import { useEffect, useEffectEvent } from 'react';
function ChatRoom({ roomId, theme }: { roomId: string; theme: string }) {
const onConnected = useEffectEvent(() => {
showNotification('Connected!', theme); // always the latest theme
});
useEffect(() => {
const connection = createConnection(roomId);
connection.on('connected', onConnected);
connection.connect();
return () => connection.disconnect();
}, [roomId]); // theme is not a dependency
}
Rules: call an Effect Event only from inside effects, and never put it in a dependency array. The updated eslint-plugin-react-hooks enforces this.
Upgrade notes
Keep React and react-dom on the latest 19.2 patch release. Security fixes have shipped in the 19.x line, so do not pin to old patches.