React 19: Actions, useActionState, useOptimistic & use
Form actions, pending states with useFormStatus, optimistic updates, the use() API and other React 19 changes.
React 19 is the biggest update in years. Its headline feature is Actions: a built-in way to handle async work like form submissions, with pending states, errors and optimistic updates built in. It also simplifies a lot of everyday code — ref is now a normal prop, context providers are shorter, and you can put <title> tags anywhere. This lesson tours the features you'll use most, including the 19.2 additions <Activity> and useEffectEvent.
Actions: async transitions#
In React 19, the function you pass to startTransition can be async. React calls these Actions. It tracks the pending state for you and sends thrown errors to the nearest error boundary:
No more manual setIsLoading(true) / finally { setIsLoading(false) }.
Form actions#
<form> now accepts a function as its action. React calls it with the form's FormData when the form is submitted — no preventDefault, no controlled inputs required — and resets uncontrolled fields after a successful submission:
Buttons can also have their own action with formAction, e.g. <button formAction={saveDraft}>Save draft</button>.
useActionState: results, errors and pending#
Most forms need to show a result: "Saved!", or a validation error. useActionState wraps an action and keeps track of what it returned:
Key points:
- Your action receives
(previousState, formData)and returns the next state. useActionStatereturns[state, formAction, isPending].- Submissions are queued: if the user submits twice, the second runs after the first, with the first's result as
previousState. - Because uncontrolled fields reset after the action, use
defaultValue={state.email}to keep what the user typed when there's an error.
useFormStatus: pending state for design-system buttons#
A reusable submit button shouldn't need an isPending prop. useFormStatus (from react-dom) reads the status of the parent form:
useFormStatus also returns data (the submitted FormData), method and action. It only works in a component rendered inside a <form>.
useOptimistic: instant feedback#
Users expect a "like" or a sent message to appear instantly. useOptimistic shows the expected result straight away while the real request runs:
useOptimistic(state, updateFn) returns the optimistic value and a function to add an optimistic update. The optimistic value is only shown while the Action is running. When it finishes, React renders the real messages again — including the saved message on success, or without it if the request threw (show an error too).
Tip: call the optimistic setter inside an Action — a form
action, or a function passed tostartTransition.
use: read promises and context#
use is a new API that reads a resource during render:
Unlike hooks, use can be called conditionally and inside loops — handy for context:
Remember the promise must be created outside render (or cached), as covered in Suspense & lazy loading.
Quality-of-life improvements#
ref is a prop. Function components receive ref like any other prop — forwardRef is no longer needed:
Ref callbacks can return a cleanup function, called when the element is removed:
Context as a provider. Render <ThemeContext value="dark"> instead of <ThemeContext.Provider value="dark">.
useDeferredValue initial value. useDeferredValue(value, initialValue) lets the first render use a cheap placeholder.
Document metadata#
Render <title>, <meta> and <link> anywhere in your components — React hoists them into <head>:
React also manages stylesheets with a precedence prop (<link rel="stylesheet" href="/chart.css" precedence="default" />) — it inserts them in order, de-duplicates them and waits for them to load before revealing Suspense content. react-dom exports resource hints such as preload, preconnect and preinit for fonts and scripts.
<Activity>: hide without losing state (19.2)#
Conditionally rendering a panel ({open && <Panel />}) destroys its state when it closes. <Activity> hides it instead:
In "hidden" mode React hides the children with display: none, cleans up their effects, and defers their updates until React has nothing else to do. It's great for tabs and for pre-rendering a page the user is likely to visit next.
useEffectEvent (19.2)#
useEffectEvent separates "reactive" logic from "event" logic inside effects — read the latest props without re-running the effect:
Upgrading from React 18#
- Update both packages:
npm install react@latest react-dom@latest. - Removed APIs:
ReactDOM.renderandhydrate(usecreateRoot/hydrateRoot),propTypesanddefaultPropson function components (use TypeScript and default parameters), string refs, and legacy context. - The
react-codemodtool can automate many of these changes.
Common mistakes#
- Calling
useFormStatusin the same component that renders the<form>— it needs to be in a child. - Forgetting that
useActionStateactions receivepreviousStatefirst, thenformData. - Expecting optimistic updates to persist after the Action ends — update real state too.
- Passing a freshly created promise to
useon every render. - Still using
forwardRefand.Providerin new code (they work, but aren't needed).
What's next#
Context and reducers cover a lot, but large apps often use a dedicated store. Next: state management with Zustand and Redux Toolkit.
Check your understanding
Quick quiz
1.What does
useActionState(action, initialState)return?2.Where must a component that calls
useFormStatus()be rendered?3.What does
useOptimisticlet you do?
Finished reading?
Mark this lesson complete to track your progress.