Every major React release gets described as a game changer, but React 19 is one of the few that genuinely alters day-to-day code. Working with React 19 in production, three things stand out as different in kind rather than degree: Actions rewire how you handle mutations and forms, the use() API changes how components consume async data and context, and the React Compiler quietly retires an entire category of manual memoization work. This post walks through each of them with an eye on what you actually do differently, not just what the release notes say.

Actions: async transitions with pending, error, and optimistic state built in

Before React 19, a typical mutation handler was a small state machine you wrote by hand: an isPending flag, an error value, a try/catch/finally block, and often a manual optimistic update with rollback logic. React 19 formalizes this pattern. Any async function used inside a transition is called an Action, and React manages the pending state, error surfacing, and sequencing for you.

The most visible expression of Actions is in forms. The <form> element now accepts a function via its action prop, and React handles submission, reads the FormData, and resets uncontrolled fields after a successful submit:

function ChangeName() {
  const [error, submitAction, isPending] = useActionState(
    async (previousState, formData) => {
      const error = await updateName(formData.get('name'));
      if (error) return error;
      redirect('/profile');
      return null;
    },
    null
  );

  return (
    <form action={submitAction}>
      <input type="text" name="name" />
      <button type="submit" disabled={isPending}>Update</button>
      {error && <p>{error}</p>}
    </form>
  );
}

useActionState gives you the last result of the Action, a wrapped action to pass to the form, and a pending flag, all in one hook. Alongside it, useFormStatus lets a deeply nested design-system button read the status of the parent form without prop drilling, and useOptimistic renders a temporary value while an Action is in flight, automatically reverting if it fails. Together these hooks replace hundreds of lines of bespoke mutation plumbing in a typical app.

The practical shift is architectural: mutation logic moves out of onClick handlers scattered through your components and into named Action functions that React can track. Your UI reads pending and error state instead of managing it.

use(): reading promises and context without ceremony

React 19 introduces use(), an API that reads the value of a resource during render. Its two accepted inputs are promises and context, and both uses are more interesting than they sound.

When you pass a promise to use(), the component suspends until the promise resolves, integrating with the nearest <Suspense> boundary for the loading state and the nearest error boundary for rejections:

function Comments({ commentsPromise }) {
  // Suspends until the promise resolves
  const comments = use(commentsPromise);
  return comments.map((c) => <p key={c.id}>{c.text}</p>);
}

function Page({ commentsPromise }) {
  return (
    <Suspense fallback={<Spinner />}>
      <Comments commentsPromise={commentsPromise} />
    </Suspense>
  );
}

The important caveat: use() does not make ad-hoc fetching in render a good idea. Promises created during render are recreated on every render, so the intended pattern is to create the promise upstream, in a Server Component, a router loader, or a caching library, and pass it down. In a Next.js app, a Server Component can start a fetch without awaiting it, hand the promise to a Client Component, and let the client suspend on it. That gives you streaming data with almost no boilerplate.

Unlike hooks, use() can be called conditionally and inside loops. That makes it a nicer way to read context too: use(ThemeContext) works inside an if block where useContext would violate the rules of hooks. Speaking of context, React 19 also lets you render <ThemeContext value="dark"> directly as a provider, retiring the <ThemeContext.Provider> spelling.

The React Compiler: memoization stops being your job

The third pillar is not an API but a build tool. The React Compiler analyzes your components and hooks and automatically memoizes values and elements, doing the work that useMemo, useCallback, and React.memo did by hand. It reached a stable release after a long beta period during which it ran in production on major Meta surfaces, and by 2026 it is a realistic default for new projects rather than an experiment.

The compiler works because it enforces what the docs call the Rules of React: components must be pure during render, props and state are immutable, and hooks are called unconditionally. Code that follows the rules gets optimized; code that breaks them is skipped rather than miscompiled, and the accompanying ESLint rules tell you why. Two consequences follow for how you write code:

  • New code gets simpler. You stop wrapping every callback in useCallback defensively. Dependency arrays for memoization largely disappear, and with them a whole class of stale-closure bugs.
  • Purity violations become real bugs. Mutating a prop, reading a ref during render, or relying on a component re-running for side effects were always wrong, but the compiler makes them consequential. Adopting it on a legacy codebase means fixing those spots first.

Keep useMemo for genuinely expensive computations if you want an explicit guarantee, but the reflexive micro-memoization that cluttered React codebases for years is no longer necessary in compiled projects.

Smaller changes that remove daily friction

Beyond the headline features, React 19 cleans up long-standing annoyances. Function components receive ref as a normal prop, so most uses of forwardRef are no longer needed. Metadata tags like <title>, <meta>, and <link> can be rendered inside any component and are hoisted to the document head automatically. Stylesheets and async scripts get first-class support with de-duplication and load ordering. Hydration errors, previously among the most cryptic messages in the ecosystem, now print an actual diff of the mismatched markup.

How to adopt it without drama

A sensible adoption path in 2026 looks like this. First, upgrade to React 19 itself; the breaking changes are modest and well documented, and codemods cover most of them. Second, migrate your most painful forms and mutations to Actions with useActionState, since that is where the immediate code deletion happens. Third, enable the React Compiler with its ESLint plugin, fix the violations it reports, and then resist the urge to hand-memoize new code. The use() API tends to arrive naturally once you work with a framework or router that hands you promises.

React 19 rewards teams that lean into its opinions. Less mutation boilerplate, less memoization ceremony, and a clearer contract between async work and UI state add up to a framework that feels smaller to use even as it does more.

Related Service

πŸ’» Web Development

Custom websites and web applications built with PHP, Laravel, WordPress, and React β€” fast, secure, scalable, and tailored to your business goals.

Explore Web Development →