image
Published on

React v19 is Stable

Authors
  • avatar
    Name
    Daniyal Afaqi
    Linkedin
    Follow

Introduction

In this post, we will discuss the new changes and improvments to React 19 stable.

What's new?

Actions

In React apps, it's common to mutate data and update state in response, such as submitting a form to change a user's name. Previously, handling pending states, errors, optimistic updates, and sequential requests had to be done manually.

For example, you could handle the pending and error state in useState:

// Before Actions
function UpdateName({}) {
  const [name, setName] = useState('')
  const [error, setError] = useState(null)
  const [isPending, setIsPending] = useState(false)

  const handleSubmit = async () => {
    setIsPending(true)
    const error = await updateName(name)
    setIsPending(false)
    if (error) {
      setError(error)
      return
    }
    redirect('/path')
  }

  return (
    <div>
      <input value={name} onChange={(event) => setName(event.target.value)} />
      <button onClick={handleSubmit} disabled={isPending}>
        Update
      </button>
      {error && <p>{error}</p>}
    </div>
  )
}

React 19 introduces support for using async functions in transitions, enabling automatic handling of pending states, errors, forms, and optimistic updates.

For example, you can use useTransition to handle the pending state for you.

// Using pending state from Actions
function UpdateName({}) {
  const [name, setName] = useState('')
  const [error, setError] = useState(null)
  const [isPending, startTransition] = useTransition()

  const handleSubmit = () => {
    startTransition(async () => {
      const error = await updateName(name)
      if (error) {
        setError(error)
        return
      }
      redirect('/path')
    })
  }

  return (
    <div>
      <input value={name} onChange={(event) => setName(event.target.value)} />
      <button onClick={handleSubmit} disabled={isPending}>
        Update
      </button>
      {error && <p>{error}</p>}
    </div>
  )
}

The async transition promptly sets the isPending state to true, performs the async request(s), and then resets isPending to false once the transitions are complete. This ensures the current UI remains responsive and interactive during data changes.

Expanding on Actions, React 19 introduces useOptimistic for managing optimistic updates and a new hook, React.useActionState, to address common use cases for Actions. Additionally, in react-dom, <form> Actions have been added to automate form management, along with useFormStatus to handle typical scenarios involving Actions in forms.

In React 19, the previous example can be streamlined to:

// Using <form> Actions and useActionState
function ChangeName({ name, setName }) {
  const [error, submitAction, isPending] = useActionState(async (previousState, formData) => {
    const error = await updateName(formData.get('name'))
    if (error) {
      return error
    }
    redirect('/path')
    return null
  }, null)

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

In the following section, we will explore each of the new Action features introduced in React 19.

New hook: useActionState

To simplify common use cases for Actions, a new hook has been introduced named useActionState:

const [error, submitAction, isPending] = useActionState(async (previousState, newName) => {
  const error = await updateName(newName)
  if (error) {
    // You can return any result of the action.
    // Here, we return only the error.
    return error
  }

  // handle success
  return null
}, null)

useActionState takes a function (the "Action") and returns a wrapped Action to invoke. This works because Actions are composable. When the wrapped Action is called, useActionState provides the most recent result of the Action as data and its pending state as pending.

React DOM: <form> Actions

In React 19, Actions are seamlessly integrated with the new <form> features in react-dom. Support has been added for passing functions as the action and formAction props of <form>, <input>, and <button> elements, enabling automatic form submission using Actions:

<form action={actionFunction}>

When a <form> Action succeeds, React will automatically reset the form for uncontrolled components. If manual form reset is needed, you can use the new requestFormReset API in React DOM.

React DOM: New hook: useFormStatus

Design systems often need design components to access information about their parent <form> without prop drilling. While Context can accomplish this, React simplifies this common pattern with a new hook called useFormStatus.

import { useFormStatus } from 'react-dom'

function DesignButton() {
  const { pending } = useFormStatus()
  return <button type="submit" disabled={pending} />
}

useFormStatus retrieves the status of the parent <form>, functioning as if the form were a Context provider.

For additional details, check out the useFormStatus documentation in the react-dom package.

New hook: useOptimistic

React 19 introduces useOptimistic, a new hook designed to streamline the implementation of optimistic updates - where you show the expected final UI state immediately while the underlying data mutation is still in progress.

function ChangeName({ currentName, onUpdateName }) {
  const [optimisticName, setOptimisticName] = useOptimistic(currentName)

  const submitAction = async (formData) => {
    const newName = formData.get('name')
    setOptimisticName(newName)
    const updatedName = await updateName(newName)
    onUpdateName(updatedName)
  }

  return (
    <form action={submitAction}>
      <p>Your name is: {optimisticName}</p>
      <p>
        <label>Change Name:</label>
        <input type="text" name="name" disabled={currentName !== optimisticName} />
      </p>
    </form>
  )
}

The useOptimistic hook provides immediate feedback by displaying the optimisticName while the backend request runs. If the updateName operation succeeds or fails, React will revert to showing the currentName.

For more information, see the docs for useOptimistic.

New API: use

React 19 introduces a new API called use for reading resources during render. When reading a promise with use, React will Suspend the component until that promise resolves.

import { use } from 'react'

function Comments({ commentsPromise }) {
  // `use` will suspend until the promise resolves.
  const comments = use(commentsPromise)
  return comments.map((comment) => <p key={comment.id}>{comment}</p>)
}

function Page({ commentsPromise }) {
  // When `use` suspends in Comments,
  // this Suspense boundary will be shown.
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <Comments commentsPromise={commentsPromise} />
    </Suspense>
  )
}

You can also read context with use, allowing you to read Context conditionally such as after early returns:

import { use } from 'react'
import ThemeContext from './ThemeContext'

function Heading({ children }) {
  if (children == null) {
    return null
  }

  // This would not work with useContext
  // because of the early return.
  const theme = use(ThemeContext)
  return <h1 style={{ color: theme.color }}>{children}</h1>
}

The use API, like hooks, can only be called during render. However, unlike hooks, it can be used conditionally. React plans to expand the capabilities of use in the future to support additional methods for consuming resources during render.

New React DOM Static APIs

React adds two new APIs in react-dom/static for static site generation: prerender and prerenderToNodeStream. These APIs enhance upon renderToString by handling data loading during static HTML generation. They're built to integrate with streaming environments including Node.js Streams and Web Streams. In Web Stream environments, developers can generate static HTML from a React tree using prerender.

import { prerender } from 'react-dom/static'

async function handler(request) {
  const { prelude } = await prerender(<App />, {
    bootstrapScripts: ['/main.js'],
  })
  return new Response(prelude, {
    headers: { 'content-type': 'text/html' },
  })
}

The new Prerender APIs will load all data before outputting the static HTML stream. While the resulting streams can be either converted to strings or sent as streaming responses, these APIs don't support progressive content streaming during loading - a feature that remains available in existing React DOM server rendering APIs.

React Server Components

Server Components

React Server Components provide a new approach where components can be rendered in advance, before bundling, in a separate environment from the client application or SSR server. This dedicated environment serves as the "server" in React Server Components. These components are flexible - they can either run once during build time on a CI server or execute per request on a web server.

React 19 incorporates all Server Components features that were previously available in the Canary channel. This enables libraries that include Server Components to specify React 19 as a peer dependency, using a react-server export condition when working with frameworks that support the Full-stack React Architecture.

For more, see the docs for React Server Components.

Server Actions

Server Actions enable Client Components to invoke asynchronous functions that execute on the server. When developers define a Server Action using the "use server" directive, the framework automatically generates a reference to the server function and passes it to the Client Component. When this function is called client-side, React sends a server request to execute the function and return its result.

Server Actions can be created in Server Components and passed as props to Client Components, or they can be imported and used in Client Components.

Improvements in React 19

ref as a prop

Starting in React 19, you can now access ref as a prop for function components:

function MyInput({ placeholder, ref }) {
  return <input placeholder={placeholder} ref={ref} />
}

//...
;<MyInput ref={ref} />

Function components in React will directly accept the ref prop, eliminating the need for forwardRef. React will provide a codemod to automatically migrate components to use the new ref prop implementation. In subsequent versions, forwardRef will be deprecated and eventually removed.

Diffs for hydration errors

React has enhanced error reporting for hydration errors in react-dom. Previously, multiple errors would be logged in DEV mode without providing details about the mismatches.

Error

React now displays a single error message that shows a diff highlighting the mismatch.

Error

<Context> as a provider

In React 19, you can render <Context> as a provider instead of <Context.Provider>:

const ThemeContext = createContext('')

function App({ children }) {
  return <ThemeContext value="dark">{children}</ThemeContext>
}

React will allow Context providers to use <Context> syntax. A codemod will be released to convert existing providers to this format. Future versions will deprecate the <Context.Provider> syntax.

Cleanup functions for refs

React now supports cleanup functions that can be returned from ref callbacks.

<input
  ref={(ref) => {
    // ref created

    // NEW: return a cleanup function to reset
    // the ref when element is removed from DOM.
    return () => {
      // ref cleanup
    }
  }}
/>

React calls the cleanup function returned from the ref callback when the component unmounts. This cleanup functionality works across DOM refs, class component refs, and useImperativeHandle.

Due to the introduction of ref cleanup functions, returning anything else from a ref callback will now be rejected by TypeScript. The fix is usually to stop using implicit returns, for example:

- <div ref={current => (instance = current)} />
+ <div ref={current => {instance = current}} />

The original code returned the instance of the HTMLDivElement and TypeScript wouldn’t know if this was supposed to be a cleanup function or if you didn't want to return a cleanup function.

You can codemod this pattern with no-implicit-ref-callback-return.

useDeferredValue initial value

React has introduced an initialValue option for useDeferredValue.

function Search({deferredValue}) {
  // On initial render the value is ''.
  // Then a re-render is scheduled with the deferredValue.
  const value = useDeferredValue(deferredValue, '');
  
  return (
    <Results query={value} />
  );
}

Support for Document Metadata

Support for Stylsheet

Support for async scripts

Support for preloading resources

Upgrade guide

See the following official React guide for Upgrade