React 19 dropped in December 2025 and it’s the biggest update since hooks. I’ve been using the RC since October, migrated “Entre Líneas” to stable last week, and I have thoughts.
Let me save you from reading the entire changelog.
The Big Three Features
1. Actions
This is the headline feature and it’s actually as good as they say.
Before React 19:
function CommentForm() {
const [isPending, setIsPending] = useState(false);
const [error, setError] = useState(null);
async function handleSubmit(e) {
e.preventDefault();
setIsPending(true);
setError(null);
try {
const formData = new FormData(e.target);
await postComment(formData);
} catch (err) {
setError(err.message);
} finally {
setIsPending(false);
}
}
return (
<form onSubmit={handleSubmit}>
<textarea name="comment" />
<button disabled={isPending}>
{isPending ? 'Posting...' : 'Post'}
</button>
{error && <p>{error}</p>}
</form>
);
}
After React 19:
function CommentForm() {
const [error, submitAction, isPending] = useActionState(
async (prevState, formData) => {
try {
await postComment(formData);
return null;
} catch (err) {
return err.message;
}
},
null
);
return (
<form action={submitAction}>
<textarea name="comment" />
<button disabled={isPending}>
{isPending ? 'Posting...' : 'Post'}
</button>
{error && <p>{error}</p>}
</form>
);
}
Less boilerplate. Built-in pending state. Automatic error handling. Progressive enhancement (works without JS!).
I’ve already refactored three forms in Pol-UI and the code is so much cleaner.
2. The use Hook (Finally!)
This one’s weird but useful. It’s a hook that can be called conditionally.
function UserProfile({ userId }) {
const user = use(fetchUser(userId));
return <div>{user.name}</div>;
}
function UserList({ userIds }) {
return userIds.map(id => {
// This would break the rules of hooks before
const user = use(fetchUser(id));
return <div key={id}>{user.name}</div>;
});
}
It works with Promises and Context. I’m still figuring out all the use cases, but it’s already saved me from some awkward component structures.
3. Document Metadata (About Time)
No more react-helmet or Next.js-specific solutions:
function BlogPost({ post }) {
return (
<>
<title>{post.title}</title>
<meta name="description" content={post.summary} />
<link rel="canonical" href={post.url} />
<article>
<h1>{post.title}</h1>
{post.content}
</article>
</>
);
}
React hoists <title>, <meta>, and <link> tags to the <head> automatically. Works in Server Components. Works in Client Components. Just works.
I removed react-helmet from two projects already.
The Smaller (But Still Cool) Stuff
ref as a Prop
No more forwardRef:
// Before
const Input = forwardRef((props, ref) => {
return <input ref={ref} {...props} />;
});
// After
function Input({ ref, ...props }) {
return <input ref={ref} {...props} />;
}
This seems small but it’s everywhere in component libraries. I deleted so much boilerplate from Pol-UI.
Context as a Prop
// Before
<ThemeContext.Provider value={theme}>
<App />
</ThemeContext.Provider>
// After
<ThemeContext value={theme}>
<App />
</ThemeContext>
Cleaner. More consistent. I like it.
Improved Hydration Errors
The error messages are actually useful now. Before, you’d get “Text content did not match” and good luck figuring out where.
Now you get:
Hydration error: Expected server HTML to contain <div> in <main>
Server: <main><p>Hello</p></main>
Client: <main><div>Hello</div></main>
This alone has saved me hours of debugging.
What I’m Excited About but Haven’t Used Yet
Async Server Components
They were experimental in React 18. Now they’re stable and the DX is incredible:
async function BlogPost({ slug }) {
const post = await db.posts.findOne({ slug });
const comments = await db.comments.find({ postId: post.id });
return (
<article>
<h1>{post.title}</h1>
<div>{post.content}</div>
<Comments data={comments} />
</article>
);
}
No useEffect. No loading states. Just async/await. Beautiful.
I haven’t used this much yet (Pol-UI is a client library) but I’m planning a Next.js project just to play with it.
Optimistic Updates (Built-in!)
function LikeButton({ postId, initialLikes }) {
const [optimisticLikes, setOptimisticLikes] = useOptimistic(
initialLikes,
(state, newLikes) => newLikes
);
async function handleLike() {
setOptimisticLikes(optimisticLikes + 1);
await likePost(postId);
}
return (
<button onClick={handleLike}>
❤️ {optimisticLikes}
</button>
);
}
The UI updates instantly. If the request fails, it reverts. No manual rollback logic.
I’m adding this to Pol-UI’s examples because it’s too good not to show off.
What Broke (Migration Pain Points)
Let’s be real, it’s not all sunshine.
1. Third-Party Libraries
Some libraries haven’t updated yet. I had issues with:
- An old version of
react-hook-form(fixed by upgrading) - A random animation library (had to replace it)
- Some Storybook addons (still waiting on updates)
Check your dependencies before upgrading production apps.
2. TypeScript Types
The types changed. A lot. Especially around ref and children.
I spent a day fixing TypeScript errors in Pol-UI. Most were simple, but there were a lot of them.
3. Strict Mode is Stricter
React 19’s Strict Mode caught some bugs I didn’t know I had. Good in the long run, annoying during migration.
Should You Upgrade?
For new projects: Yes, absolutely. Start with React 19.
For existing projects: Depends.
Upgrade if:
- You’re already on React 18
- You have good test coverage
- You’re okay spending a day on migration
- You want the new features
Wait if:
- You’re on React 17 or older (upgrade to 18 first)
- You have a lot of third-party dependencies
- You’re about to ship something critical
- You don’t have time for potential issues
I upgraded Pol-UI because it’s a library and I want to support the latest React. For my client projects, I’m waiting until Q1 2026 when the ecosystem catches up.
My Favorite Part: It Feels Like React Again
React 18 felt like a transitional release. Server Components were experimental. Concurrent features were confusing. The mental model was shifting.
React 19 feels done. The features work together. The docs are clear. The upgrade path is smooth.
It reminds me of when hooks came out, a clear improvement that makes you wonder how you lived without it.
What I’m Building With It
I’m working on a new example app for Pol-UI that uses:
- Actions for all forms
- Optimistic updates for interactions
- Server Components for data fetching
- The new metadata API
It’s going to be a task manager (original, I know) but the code is so clean compared to the React 18 version.
I’ll open-source it when it’s done. Should be a good reference for “modern React in 2026.”
Final Thoughts
React 19 is good. Really good. The Actions API alone is worth the upgrade.
Is it perfect? No. Will some things break? Probably. But the direction is right and the features are solid.
If you’re on the fence, try it in a side project first. Build something small. See how it feels.
I think you’ll like it.
Now go ship something with React 19. And maybe give Pol-UI a try while you’re at it 😉
Related Articles
React Component Composition Patterns: Compound Components, Render Props & More 🧩
Master React component composition patterns with real-world examples. Learn compound components, render props, slots, and context patterns. Reduce props, increase flexibility, and build better design systems.
TypeScript Generics in React Components: Complete Guide with Examples 🎯
Learn how to use TypeScript generics in React components with practical examples. Build reusable, type-safe components for Select, Table, and more. Includes best practices and common pitfalls to avoid.
Migrating from Next.js to Astro: Portfolio Performance Case Study 🚀
Complete guide to migrating a portfolio from Next.js to Astro. Reduced JavaScript from 85KB to 3KB, improved FCP by 66%. Includes performance metrics, migration steps, and honest pros/cons comparison.