Back to blog

What is New in React 19: Action Hooks, Server Components, and Asset Loading

React 19 represents a major milestone in the evolution of the library, focusing on streamlining full-stack integration, automating pending states, and optimizing resource loading.

Instead of introducing purely incremental updates, React 19 redefines how we handle data mutations, loading states, and document head metadata. In this guide, we will explore the core features of React 19, focusing on the new Actions API, dynamic hooks like useActionState, the use API, and native asset loading.

The Actions API: Simplifying Async State

In React 18, handling an asynchronous state (like submitting a form) required manual state management. You had to manage loading states (isPending), errors, and success states yourself.

React 19 introduces Actions. When you pass an asynchronous function to a DOM element (like <form action={myAction}>), React automatically manages the pending state, error handling, and completion flow for you.

Here is how simple form submission becomes using React 19 Actions:

// React 19 Form Submission
'use client';

import React from 'react';

async function updateNameAction(formData: FormData) {
  const name = formData.get('name');
  await fetch('/api/update-name', {
    method: 'POST',
    body: JSON.stringify({ name }),
  });
}

export function UpdateProfileForm() {
  return (
    <form action={updateNameAction}>
      <input name="name" type="text" placeholder="Enter your name" required />
      <button type="submit">Update Name</button>
    </form>
  );
}

Behind the scenes, React intercepts the submission, executes the async function, and manages the lifecycle automatically.

New Action Hooks

To give you granular control over these Actions, React 19 introduces three powerful hooks:

1. useActionState

Formerly known as useFormState, this hook is the standard tool for managing Action lifecycles. It accepts an Action function and an initial state, and returns the current state, a wrapped dispatcher, and an isPending boolean.

import { useActionState } from 'react';

async function subscribeNewsletter(prevState: any, formData: FormData) {
  try {
    await sendRequest(formData.get('email'));
    return { success: true, message: 'Subscribed!' };
  } catch (err) {
    return { success: false, message: 'Subscription failed' };
  }
}

export function NewsletterForm() {
  const [state, formAction, isPending] = useActionState(subscribeNewsletter, null);

  return (
    <form action={formAction}>
      <input name="email" type="email" required />
      <button type="submit" disabled={isPending}>
        {isPending ? 'Submitting...' : 'Subscribe'}
      </button>
      {state && <p>{state.message}</p>}
    </form>
  );
}

2. useFormStatus

This hook acts as a context reader for forms. It allows child components to read their parent form’s submission status (such as pending) without prop-drilling or manually passing state.

import { useFormStatus } from 'react-dom';

export function SubmitButton() {
  const { pending } = useFormStatus();

  return (
    <button type="submit" disabled={pending}>
      {pending ? 'Saving Changes...' : 'Save'}
    </button>
  );
}

3. useOptimistic

This hook allows you to perform optimistic updates. During async data mutations, you can update the UI instantly to assume success, automatically reverting the changes if the server action fails.

import { useOptimistic } from 'react';

export function MessageList({ messages, sendMessage }) {
  const [optimisticMessages, addOptimisticMessage] = useOptimistic(
    messages,
    (state, newMessage: string) => [...state, { text: newMessage, sending: true }]
  );

  async function formAction(formData: FormData) {
    const text = formData.get('message') as string;
    addOptimisticMessage(text);
    await sendMessage(text);
  }

  return (
    <div>
      {optimisticMessages.map((msg, i) => (
        <p key={i}>
          {msg.text} {msg.sending && '(sending...)'}
        </p>
      ))}
      <form action={formAction}>
        <input name="message" type="text" />
        <button type="submit">Send</button>
      </form>
    </div>
  );
}

The "use" API: Conditional Promises and Context

React 19 introduces a new API called use. Unlike traditional hooks, use can be called conditionally and inside loops. It allows you to read a Promise or a Context directly in your render method.

If you pass a Promise to use, React suspends the component until the promise resolves, integrating seamlessly with React Suspense:

import { use } from 'react';

function UserProfile({ userPromise }) {
  // Read promise value directly during render
  const user = use(userPromise);
  return <h1>Welcome back, {user.name}!</h1>;
}

Native Document Metadata and Asset Loading

Document Metadata Support

In React 18, managing title, meta, and link tags required libraries like React Helmet. In React 19, you can declare these tags inside any component in your component tree, and React will automatically hoist them to the document head:

export function BlogPost({ post }) {
  return (
    <article>
      <title>{post.title}</title>
      <meta name="description" content={post.excerpt} />
      <h1>{post.title}</h1>
      <p>{post.body}</p>
    </article>
  );
}

Asset Loading

React 19 integrates resource preloading directly into the rendering lifecycle. It introduces native APIs to prefetch and load assets:

  • preconnect: Warm up connections to external domains.
  • prefetchDNS: Resolve DNS lookups early.
  • preload: Start downloading stylesheets, scripts, or fonts.
  • preinit: Preload and execute a script or compile a stylesheet.

These resources are deduplicated and managed by React, ensuring they load efficiently without blocking critical component rendering.

Conclusion

React 19 represents a shift in how frontend engineers build applications. By standardizing Actions for asynchronous operations, introducing new hooks like useActionState and useOptimistic for state coordination, and hoisting metadata and assets natively, React 19 significantly improves developer experience and application speed. Embrace these updates to declutter your state management and build modern, highly optimized user experiences.