- Published on
React v19 is Stable
- Authors
- Name
- Daniyal Afaqi
- 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.
useActionState
New hook: 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
.
<form>
Actions
React DOM: 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.
React now displays a single error message that shows a diff highlighting the mismatch.
<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