From Frontend to DevOps
Expanding Your skillset for the Modern Job Market
Let's take a look at the improvements and new parts of React 19
Eddy Vinck - 6 min read
The beta release of React 19 has just been released. There are some great quality of life improvements and some new things.
There are quite a few improvements to the existing parts of React:
forwardRef
forwardRef
A ref
can now be passed as a prop. This was possible before using a prop name other than ref
such as buttonRef
, but now you can access the ref
as a prop in your component without forwardRef
:
function MyInput({placeholder, ref}) { return <input placeholder={placeholder} ref={ref} /> }
React 19 has official support for metadata inside components. Before React 19 you would need a package like
to achieve this.With the example below, the meta title and other related items will be updated inside the <head>
tag of your page.
function BlogPost({post}) { return ( <article> <h1>{post.title}</h1> <title>{post.title}</title> <meta name="author" content="Josh" /> <link rel="author" href="https://twitter.com/joshcstory/" /> <meta name="keywords" content={post.keywords} /> <p> Eee equals em-see-squared... </p> </article> ); }
Stylesheets are supported too:
function Component() { return ( <Suspense fallback="loading..."> <link rel="stylesheet" href="foo" precedence="default" /> <article class="foo-class bar-class"> {...} </article> </Suspense> ) }
Errors were previously thrown twice by an Error Boundary, and then logged using console.error
to show you more information about the error. In React 19, you will see a single error with all the information you need. This is a very welcome change as it declutters your debugging console.
useActionState
hookreact-dom
and useFormStatus
useOptimistic
hookuse
APITraditionally you could already use useState
or alternative state management solutions to manage all the various data, loading, and error states in your forms.
React 18 released the
. This hook lets you update state without blocking the UI. This helps keep the UI updates responsive on slower end-user devices like older phones, and was a useful addition to managing state.The useTransition
hook can now start a transition with an async
function. In the following example, React will manage the state of isPending
for you, which can save you some boilerplate logic you would previously have to write using useState
. While your async
function is running isPending
will be true
. After the async
function succeeds isPending
will automatically be set to false
.
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> ); }
useActionState
hookAsyncronous transitions are called "actions" by convention in React. A new useActionState
hook was introduced that helps handle the most common use-cases for actions.
The useActionState
can handle error
and isPending
states. If you've used
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> ); }
react-dom
Updates have been made to react-dom
that make implementing these actions even easier.
You can now pass an async
function to a form's action
attribute:
<form action={actionFunction}>
When the actionFunction
returns successfully, the form will be reset automatically for uncontrolled components (components where you don't manage the state via useState
or other state management options).
You can also add the action
to an <input>
or a <button>
.
A new hook useFormStatus
is now also available, which lets you check the form status in child components in a form:
import {useFormStatus} from 'react-dom'; function DesignButton() { const {pending} = useFormStatus(); return <button type="submit" disabled={pending} /> }
I've used this hook before in a test application built with Next.js when the hook was still experimental, so it's good to see this hook available without the experimental warning.
Here is a snippet from an
component I made which utilizes in Next.js:"use client"; import { experimental_useFormStatus as useFormStatus } from "react-dom"; export function SubmitButton() { const { pending } = useFormStatus(); return ( <button className="bg-slate-600 p-2 px-6 rounded-md" disabled={pending}> {pending ? "Submitting..." : "Submit"} </button> ); }
What I like about this, is that it removes the need for a lot of boilerplate code originally required to manage your form state. Passing props or providing a custom React Context is not necessary in a lot of cases now, which is great!
useOptimistic
hookThere is a new hook that helps with managing optimistic UI. The Remix documentation has a good defintion for optimistic UI if you're unsure what it means:
Optimistic UI: Optimistic UI enhances perceived speed and responsiveness by immediately updating the UI with an expected state before the server's response is received
— Remix documentation ()
In the following example, the useOptimistic
will render the submitted name immediately when the form submits. When the submit action finishes or errors, React will then continue to use the currentName
again.
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> ); }
use
APIReact has introduced a new flexible API with multiple use-cases. It's just called use
, and according to the official React 19 beta announcement article you can use it to "read resources in render." Practically, it means you can use it for reading promises (which is useful when you're using
import {use} from 'react'; import ThemeContext from './ThemeContext' function Comments({commentsPromise}) { // `use` will suspend until the promise resolves. const comments = use(commentsPromise); const theme = use(ThemeContext); return comments.map(comment => <p style={{color: theme.color}} 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> ) }
See the official
page for more information.Web components (or
) are now fully supported in React. Previously there were some issues when using custom elements in React, but now they should behave as you would expect when doing Server Side Rendering or Client Side Rendering.React 19 is in beta right now. The React team recommends waiting until frameworks have implemented it under the hood before you upgrade to React 19 for your application.
This release is mostly to prepare library authors, so that all your favorite React libraries will be ready when React 19 goes out of beta. There are great improvements and features in this new React version, but we will have to wait until our favorite React frameworks have implemented them.
For the full release notes, see the full
. If you're curious what changes you need to make when upgrading, be sure to take a look at .