React 19 was released in December 2024 and is the current major stable version. It focused heavily on simplifying data mutations, forms, and async data — areas that previously needed a lot of manual useState/useEffect boilerplate.
Actions — functions that handle async transitions for you
An Action is any async function passed to something like a <form>'s action prop, a transition, or useActionState. React automatically tracks its pending state, errors, and optimistic updates, so you no longer hand-wire loading flags for every mutation.
<form action={async (formData) => {
const name = formData.get('name');
await saveStudent(name);
}}>
<input name="name" />
<button type="submit">Save</button>
</form>
useActionState — form state without the boilerplate
import { useActionState } from 'react';
async function updateName(previousState, formData) {
const name = formData.get('name');
if (!name) return { error: 'Name is required' };
await saveStudent(name);
return { error: null };
}
function ProfileForm() {
const [state, formAction, isPending] = useActionState(updateName, { error: null });
return (
<form action={formAction}>
<input name="name" />
{state.error && <p className="error">{state.error}</p>}
<button disabled={isPending}>{isPending ? 'Saving...' : 'Save'}</button>
</form>
);
}
useFormStatus — read the parent <form>'s pending state
Useful for building a reusable submit button without prop drilling the pending flag:
import { useFormStatus } from 'react-dom';
function SubmitButton() {
const { pending } = useFormStatus();
return <button disabled={pending}>{pending ? 'Saving...' : 'Save'}</button>;
}
useOptimistic — instant UI feedback before the server confirms
import { useOptimistic } from 'react';
function CommentList({ comments, addComment }) {
const [optimisticComments, addOptimisticComment] = useOptimistic(
comments,
(state, newComment) => [...state, { ...newComment, sending: true }]
);
async function formAction(formData) {
const text = formData.get('comment');
addOptimisticComment({ text });
await addComment(text); // real request; UI already shows the comment
}
return (
<>
{optimisticComments.map((c, i) => (
<p key={i} style={{ opacity: c.sending ? 0.5 : 1 }}>{c.text}</p>
))}
<form action={formAction}><input name="comment" /></form>
</>
);
}
use() — read a promise or context conditionally
use() is a new primitive that can read the value of a Promise or a Context, and — unlike other hooks — it can be called conditionally or in loops.
import { use, Suspense } from 'react';
function StudentName({ studentPromise }) {
const student = use(studentPromise); // suspends until resolved
return <h2>{student.name}</h2>;
}
function Page({ studentPromise }) {
return (
<Suspense fallback={<p>Loading...</p>}>
<StudentName studentPromise={studentPromise} />
</Suspense>
);
}
ref as a regular prop — no more forwardRef
// React 19: just declare ref as a normal prop
function Input({ ref, ...props }) {
return <input ref={ref} {...props} />;
}
// old React 18 way (still works, but no longer necessary)
const Input = forwardRef((props, ref) => <input ref={ref} {...props} />);
Other React 19 highlights
- Server Components became a stable feature for framework authors to build on (used by Next.js App Router).
- Document metadata —
<title>,<meta>, and<link>tags can now be rendered directly inside any component and React hoists them into<head>automatically. - Stylesheet & async script support — React manages loading order and de-duplication of
<link rel="stylesheet">and<script async>tags rendered from components. - Better hydration error messages — mismatches between server and client HTML now show a readable diff instead of a generic warning.
- Custom Elements (Web Components) — improved, more complete support for passing props and listening to events on custom elements.