New client-side hooks coming to React 19

François Zaninotto
François ZaninottoJanuary 23, 2024
#react#js

Contrary to popular belief, the React core team isn't solely focused on React Server Components and Next.js. New client-side hooks are coming in the next major version of React, React 19. They focus on two major pain points in React: data fetching and forms. These hooks will enhance the productivity of all React developers, including those working on Single-Page Apps.

Without further ado, let's dive into the new hooks!

Note: These hooks are only available in React’s Canary and experimental channels. They should be part of the upcoming React 19, but the API may change before the final release.

use(Promise)

This new hook is the official API for "suspending" on the client. You can pass it a promise, and React will suspend on it until it resolves. The basic syntax, taken from the React use documentation, is:

import { use } from 'react';

function MessageComponent({ messagePromise }) {
    const message = use(messagePromise);
    // ...
}

The great news is that this hook can be used for data fetching. Here is a concrete example with data fetching on mount and on click on a button. The code doesn't use a single useEffect:

Remember this warning in the <Suspense> documentation?

Suspense-enabled data fetching without the use of an opinionated framework is not yet supported.

Well, it's no longer true for React 19.

This new use hook has a hidden power: Unlike all other React Hooks, use can be called within loops and conditional statements like if.

Does that mean that we no longer need to use a third-party library like TanStack Query to fetch data on the client side? Well, it remains to be seen, as Tanstack Query does more than just resolving a Promise.

But it's a great step in the right direction, and it will make it easier to build Single-page apps based on REST or GraphQL APIs. I'm super enthusiastic about this new hook!

Read more about the use(Promise) hook in the React documentation.

use(Context)

The same use hook can be used to read a React Context. It's exactly like useContext, except it can be called within loops and conditional statements like if.

import { use } from 'react';

function HorizontalRule({ show }) {
    if (show) {
        const theme = use(ThemeContext);
        return <hr className={theme} />;
    }
    return false;
}

This will simplify the component hierarchy for some use cases, as the only way to read a context in a loop or a conditional was to split the component in two.

It's also a great evolution in terms of performance, as you can now conditionally skip the re-rendering of a component, even though the context has changed.

Read more about the use(Context) hook in the React documentation.

Form Actions

This new feature enables you to pass a function to the action prop of a <form>. React will call this function when the form is submitted:

<form action={handleSubmit} />

Remember that if you add a <form action> prop in React 18, you get this warning:

Warning: Invalid value for prop action on <form> tag. Either remove it from the element or pass a string or number value to keep it in the DOM.

That's no longer true in React 19, where you can write a form like this:

The addToCart function is not a Server Action. It is called on the client side, and it can be an async function.

This will greatly simplify the handling of AJAX forms in React - e.g., in a search form. But again, this may not be enough to get rid of third-party libraries like React Hook Form, which does much more than just handle form submission (validation, side effects, etc).

Tip: You may find some usability issues in the example above (submit button not disabled while submitting, missing confirmation message, late update of the cart). Fortunately, more hooks are coming to help with this use case. Read on!

You can read more about the <form action> prop in the React documentation.

useFormState

This new hook aims to help with the async form action feature described above. Call useFormState to access the return value of an action from the last time a form was submitted.

import { useFormState } from 'react-dom';
import { action } from './action';

function MyComponent() {
    const [state, formAction] = useFormState(action, null);
    // ...
    return <form action={formAction}>{/* ... */}</form>;
}

For instance, this lets you display a confirmation or an error message returned by the form action:

Note: useFormState must be imported from react-dom, not react.

You can read more about the useFormState hook in the React documentation.

useFormStatus

useFormStatus lets you know if a parent <form> is currently submitting or has submitted successfully. It can be called from children of the form, and it returns an object with the following properties:

const { pending, data, method, action } = useFormStatus();

You can use the data property to display what data is being submitted by the user. You can also display a pending state, as in the following example where the button is disabled while the form is submitting:

Note: useFormState must be imported from react-dom, not react. Also, it only works when the parent form uses the action prop described above.

Together with useFormState, this hook will improve the user experience of client-side forms without cluttering your components with useless context of effects. You can read more about the useFormStatus hook in the React documentation.

useOptimistic

This new hook lets you optimistically update the user interface while an action is being submitted.

import { useOptimistic } from 'react';

function AppContainer() {
    const [optimisticState, addOptimistic] = useOptimistic(
        state,
        // updateFn
        (currentState, optimisticValue) => {
            // merge and return new state
            // with optimistic value
        },
    );
}

In the cart example above, we could use this hook to display the cart with the new item added before the AJAX call is finished:

Optimistically updating the UI is a great way to improve the user experience of a web app. This hooks helps a lot with this use case. You can read more about the useOptimistic hook in the React documentation.

Bonus: Async Transitions

React's Transition API lets you update the state without blocking the UI. For instance, it lets you cancel a previous state change if the user changes their mind.

The idea is to wrap a state change with a call to startTransition.

function TabContainer() {
    const [isPending, startTransition] = useTransition();
    const [tab, setTab] = useState('about');

    function selectTab(nextTab) {
        // instead of setTab(nextTab), put the state change in a transition
        startTransition(() => {
            setTab(nextTab);
        });
    }
    // ...
}

The following example shows a tab navigation using this Transitions API. Click “Posts” and then immediately click “Contact”. Notice that this interrupts the slow render of “Posts”. The “Contact” tab shows immediately. Because this state update is marked as a transition, a slow re-render did not freeze the user interface.

The useTransition hook is already available in React 18.2. What's new in React 19 is that you can now pass an async function to startTransition, which is awaited by React to start the transition.

This is useful for submitting data through an AJAX call and rendering the result in a transition. The transition pending state starts with the async data submission. It is already used in the form actions feature described above. This means that React calls the <form action> handler wrapped in startTransition, so it doesn’t block the current page.

This feature isn't yet documented in the React documentation, but you can read more about it in the pull request.

Conclusion

All of these features work in client-only React apps, for instance in apps bundled with Vite. You don't need an SSR framework like Next or Remix to use them - although they also work with server-integrated React apps.

With these features, data fetching and forms become significantly easier to implement in React. However, creating a great user experience involves integrating all these hooks, which can be complex. Alternatively, you can use a framework like react-admin where user-friendly forms with optimistic updates are built-in.

Why are these features coming to React 19 instead of React 18.3? It seems that there will not be a 18.3 release because these features include some minor breaking changes.

When is React 19 coming? There is no ETA yet, but all the features mentioned in this post are already working. I wouldn't recommend using them yet, though - using a canary release in production is not a good idea (even if Next.js does it).

It's really nice to see that the React core team is working on improving the developer experience of all React developers, not only those working on SSR apps. It also seems they're listening to the community feedback - data fetching and form handling are very common pain points.

I'm looking forward to seeing these features in a stable release of React!

Did you like this article? Share it!