React 19 Beta: Major Updates to Async Transactions

React 19 Beta: Major Updates to Async Transactions
Photo by Lautaro Andreani / Unsplash

The beta release of React 19 is officially here! This is the latest version of Meta's JavaScript Library that is used for rendering amazing looking user interfaces. This update includes features such as async functions in transactions and the ability of accessing ref as a prop for function components.

The React 19 Beta was released last week on April 25th, 2024. The Developers of React has released a nice Beta Upgrade Guide for those who want to try out the new features and see how it works for them.

The React 19 beta was unveiled April 25. A React 19 beta upgrade guide has been published.

The newest version is adding support for using async functions in transactions. This means that they are able to handle pending states, forms, errors, and optimistic updates automatically without much more work to application code. Any function that uses async transactions are called by Actions. When a JavaScript Developer builds on top of the Actions, React 19 is introducing useOptimistic to manage the optimistic updates. There is also a new hook – which is going to be React.useActionState to handle common cases for Actions. There are also Actions that are going to be integrated with the new <form> feature for react-dom in React 19.

In the following example code, I am going to use the function handleAddToCart as an async function that checks item availability before sending an action to add the item to the cart. The useAction hook binds this function to user interface (UI) events, providing seamless integration of async operations with user interactions. So we are going to consider a shopping cart where users can add items, and the application needs to check availability asynchronously before updating.

async function checkAvailability(item) {
  const response = await fetch(`/api/check/${item.id}`);
  const { available } = await response.json();
  return available;
}

async function handleAddToCart(action, item) {
  const isAvailable = await checkAvailability(item);
  if (isAvailable) {
    action.send({ type: 'ADD_ITEM', item });
  } else {
    alert('Item is not available');
  }
}

function CartComponent() {
  const cartAction = useAction(handleAddToCart);

  return (
    <div>
      {products.map(item => (
        <button key={item.id} onClick={() => cartAction.call(item)}>
          Add to Cart
        </button>
      ))}
    </div>
  );
}

The useOptimistic and useActionState hooks introduce a powerful way to handle optimistic updates — a technique where the user interface (UI) is updated as if the desired operation has already succeeded, without waiting for the server response.

Here's a code example right below of how you might use useOptimistic to optimistically update a user profile, and useActionState to handle the state of the operation.

function UserProfile({ userId }) {
  const [user, setUser] = useOptimistic(null);
  const fetchUser = async () => {
    const response = await fetch(`/api/user/${userId}`);
    const userData = await response.json();
    setUser(userData);
  };

  useEffect(() => {
    fetchUser();
  }, [userId]);

  async function handleUpdateProfile(updatedData) {
    setUser(prev => ({ ...prev, ...updatedData })); // Optimistic update
    try {
      await fetch(`/api/user/${userId}`, {
        method: 'POST',
        body: JSON.stringify(updatedData),
        headers: { 'Content-Type': 'application/json' },
      });
    } catch (error) {
      alert('Failed to update profile');
      fetchUser(); // Re-fetch user to get current server state
    }
  }

  return (
    <div>
      <input
        value={user?.name || ''}
        onChange={(e) => handleUpdateProfile({ name: e.target.value })}
      />
      {/* Other fields */}
    </div>
  );
}

In next following code example, I will create a simple user registration form that uses React 19’s new form handling features. This form will collect user information, validate inputs, and submit the data to a server. Then I will use useForm to handle form state and Action to manage form submissions.

import React from 'useForm', { Action } from 'react';

function RegistrationForm() {
  const { formData, handleInputChange, handleSubmit } = useForm({
    initialValues: {
      username: '',
      email: '',
      password: ''
    },
    onSubmit: async (values, action) => {
      try {
        // Simulate an API call
        const response = await fetch('/api/register', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify(values),
        });
        if (!response.ok) throw new Error('Registration failed');
        alert('Registration successful!');
      } catch (error) {
        action.fail(error.message); // Handle submission failure
      }
    },
    validate: values => {
      const errors = {};
      if (!values.username) errors.username = 'Username is required';
      if (!values.email.includes('@')) errors.email = 'Email is invalid';
      if (values.password.length < 6) errors.password = 'Password must be at least 6 characters';
      return errors;
    }
  });

  return (
    <form onSubmit={handleSubmit} noValidate>
      <div>
        <label htmlFor="username">Username:</label>
        <input id="username" name="username" type="text" value={formData.username} onChange={handleInputChange} />
        {formData.errors.username && <p>{formData.errors.username}</p>}
      </div>
      <div>
        <label htmlFor="email">Email:</label>
        <input id="email" name="email" type="email" value={formData.email} onChange={handleInputChange} />
        {formData.errors.email && <p>{formData.errors.email}</p>}
      </div>
      <div>
        <label htmlFor="password">Password:</label>
        <input id="password" name="password" type="password" value={formData.password} onChange={handleInputChange} />
        {formData.errors.password && <p>{formData.errors.password}</p>}
      </div>
      <button type="submit">Register</button>
    </form>
  );
}

The release also includes all of major React Server Components features from the Canary channel. This means that libraries that are shipped with Server Components now can target the newest version – React 19 in this case – as a peer dependency with react-server export conditions that are for use in frameworks. This also means that it will support the Full-Stack React Architecture by default.

Other Notable Improvements in React 19

  • A new API to read resources in render, called use, is introduced.
  • Error reporting has been improved for hydration errors in react-dom.
  • Better support is offered by async scripts; they can be rendered anywhere in a component tree.
  • APIs are offered for loading and preloading browser resources for building experiences not held by inefficient resource loading.
  • Hydration has been improved to account for third-party scripts and browser extensions.
  • Error handling has been improved to remove duplication and provide options for handling caught and uncaught errors.
  • Support is being added for rendering document metadata tags in components natively.
  • <Context> can be rendered as a provider instead of <Context.Provider>.
  • Cleanup functions can be returned from ref callbacks.
  • An initialValue option has been added to useDeferredValue.