Home / Articles

React 19 Actions in Practice: What useActionState, use() and the Compiler Retire

React 19 actions retire a lot of code: form handlers, optimistic updates, forwardRef and manual memoization. What useActionState, use() and the compiler change.

Publisher: REACTAPP.TOP6 min read1446 words

This article is published in English.

Sources: React 19 Shipped Nine Months Ago. Most Teams Are Still Writing Code It Made Obsolete.

Plenty of teams bumped the React version number and left everything else alone. The loading flags, error handling and optimistic updates they wrote years ago still sit in the codebase, because the upgrade never broke them. It just made them redundant. This is a tour of the hand-written code that React 19 now covers for you.

The pattern that keeps showing up

Open almost any form component in a React 19 project and you'll find the same shape. There are three pieces of state, one for loading, one for the error and one for success. There's an async submit handler that calls e.preventDefault(), switches loading on, clears the old error, awaits the request, flips success on in a try, copies the message into error in a catch, and switches loading off in a finally.

That recipe has been standard since React became popular, and it works. On React 19 it's also unnecessary. One hook now owns the pending, error and result handling, including the ordering of sequential requests. Most teams never noticed, since upgrading didn't force a change.

What is stable and what isn't

A quick map first, because some articles describe features as shipped when they aren't.

Available in stable React 19.x:

  • Actions and the hooks built on them: useActionState, useFormStatus and useOptimistic.
  • The use() API for reading promises and context during render.
  • ref as an ordinary prop, which makes forwardRef legacy.
  • Native document metadata: <title>, <meta> and <link> tags rendered from components.
  • The React Compiler, which reached 1.0 in October 2025 and supports versions back to React 17.
  • Additions from 19.2 such as <Activity> and useEffectEvent.

Still Canary or experimental at the time of writing: the <ViewTransition> component and Fragment Refs. Treat any piece that calls those shipped as out of date, and check the release notes for the version you actually install.

Actions: the new model for async state

The organizing idea of React 19 is the Action, an async function handed to a transition. React takes charge of everything surrounding it: the pending state, error handling, optimistic updates and form resets, all the things teams used to write manually.

useActionState, useFormStatus and useOptimistic are not three unrelated features. They are three views of that one mechanism.

useActionState replaces the three-flag pattern

Instead of the loading, error and success trio, you write an action function that receives the previous state and the form data and returns the next state. You pass it, with an initial state, to useActionState. The hook returns three things: the current state, a function to hand to the form, and an isPending boolean.

The component then does three simple things. The form's action prop receives the returned function directly. The button is disabled while isPending is true. And any message is read from the returned state. There's no loading state, no error state, no try, catch or finally, no preventDefault and no manual resetting.

Passing a function to action is deliberate. HTML forms have always had an action attribute, and React 19 reclaimed it. That supports progressive enhancement, where a form can submit even without JavaScript and React upgrades the behavior once scripts load.

useFormStatus lets a button know its form

The tidiest use of useFormStatus is a reusable submit button. Inside it, the hook returns a pending flag describing the parent form, with no props passed down. The button disables itself and swaps its label to something like "Saving" while the form is pending. Drop that component anywhere inside any form and it just works, which removes prop drilling for a very common need. One caveat: the hook reads the status of a form that wraps the component, so it must be rendered inside the form, not beside it.

useOptimistic makes instant feedback manageable

Optimistic updates show the user the expected result right away, before the server confirms, and correct themselves if the request fails. Done by hand, they demand temporary IDs, a separate list of pending items, merging logic, and rollback code in an error branch. It's easy to get subtly wrong.

With useOptimistic, you give the hook the real list plus a small reducer describing how to add an item with a pending marker. Inside the form's action, you call the setter it returns and then await the save. The interface shows the new item immediately, perhaps at reduced opacity while pending. When the action finishes, the real data replaces the optimistic copy, and if the save fails, React reverts to the previous state on its own. No catch block or manual reversal is required.

use(): reading resources conditionally

use() is a new API that reads a resource, such as a promise or a context, during render. Unlike every other hook, it can be called conditionally. A component can read a user, return early when details are hidden, and only then read preferences. Wrap the tree in a Suspense boundary with a fallback, and React suspends until the data arrives.

One constraint matters. Promises passed to use() must be stable across renders, so create them outside the component or go through a caching layer such as React Query or SWR, or even a simple module-level map keyed by ID. A promise recreated on every render keeps the fallback on screen forever.

ref as a prop

This change is straightforward. Function components now receive ref as a normal prop, so you pass it down to the underlying input without wrapping the component in forwardRef. Behavior is identical, with one fewer wrapper and one fewer import. Existing forwardRef code keeps working, since the deprecation is gradual and non-breaking, but new components shouldn't use it.

The React Compiler

The compiler is the quietest upgrade of all. It analyzes components at build time and applies the memoization teams usually either hand-roll inconsistently with useMemo, useCallback and React.memo, or skip entirely. You write plain components and the compiler inserts the caching correctly.

To try it, install the Babel plugin babel-plugin-react-compiler and list it in your Babel config's plugins. Because it supports React 17 and 18 as well, you can adopt it without upgrading React first, which helps teams blocked on a version bump but wanting the performance benefits.

A security note for server components

Security advisories covering denial of service and source-code exposure affected the react-server-dom-* packages around the start of 2026, and patched releases were published for each supported line. If you use React Server Components, verify your installed version against the current advisories and pin to a patched release or newer. Don't rely on memory of which version is safe.

What to do this week

  • New forms: reach for useActionState from day one, defining an action, giving it to the hook and reading isPending from what comes back, with no hand-made flags at all.
  • Existing handlers: don't rewrite everything at once. Pick one, convert it to an Action and see how it reads. The readability gain is usually obvious enough to set the migration priority itself.
  • The compiler: add it to your build configuration. It's a small change, works on existing code and needs no rewrites, which makes it the easiest React 19-era win with the widest effect.
  • Server components: check your version against the security fixes.

The headline features drew attention at release. The bigger shift is underneath: Actions as the standard model for any async state change, which quietly removes a large share of the code React developers write every day. Many teams upgraded the number, and the code is still waiting.