React 19 New Features Complete Guide: Actions, use(), Server Components, and Compiler Optimizations
React 19: A Framework-Level Paradigm Upgrade
React 19 is not just an API update—it's a positioning shift from "library" to "full-stack framework." Server Components, Actions, and Compiler redefine how React applications are written.
| Feature | Problem Solved | Impact Scope |
|---|---|---|
| Form Actions | Boilerplate code for form submissions | All form interactions |
| use() Hook | Conditional data loading | Data fetching patterns |
| Server Components | Client bundle size | Full architecture |
| React Compiler | Manual memo optimization | Performance |
| ref as prop | forwardRef boilerplate | Component API |
| Document Metadata | SEO/head management | SSR applications |
1. Form Actions: Goodbye Form Boilerplate
Traditional Approach
function LoginForm() {
const [pending, setPending] = useState(false);
const [error, setError] = useState(null);
async function handleSubmit(e) {
e.preventDefault();
setPending(true);
setError(null);
try {
const formData = new FormData(e.target);
await login(formData.get('email'), formData.get('password'));
} catch (err) {
setError(err.message);
} finally {
setPending(false);
}
}
return (
<form onSubmit={handleSubmit}>
<input name="email" type="email" />
<input name="password" type="password" />
<button disabled={pending}>{pending ? 'Logging in...' : 'Login'}</button>
{error && <p>{error}</p>}
</form>
);
}
React 19 Approach
async function loginAction(formData) {
// Executes directly on the server, no manual pending/error management
await login(formData.get('email'), formData.get('password'));
}
function LoginForm() {
return (
<form action={loginAction}>
<input name="email" type="email" />
<input name="password" type="password" />
<SubmitButton />
</form>
);
}
// useActionStatus auto-tracks submission state
function SubmitButton() {
const { pending } = useActionStatus();
return <button disabled={pending}>{pending ? 'Logging in...' : 'Login'}</button>;
}
Code reduction: ~60%. No more onSubmit, preventDefault, or manual pending/error state.
useActionState: Managing Form State
function LoginForm() {
const [state, formAction, isPending] = useActionState(
async (prevState, formData) => {
try {
await login(formData.get('email'), formData.get('password'));
return { success: true };
} catch (err) {
return { error: err.message };
}
},
null
);
return (
<form action={formAction}>
<input name="email" type="email" />
<input name="password" type="password" />
<button disabled={isPending}>Login</button>
{state?.error && <p className="error">{state.error}</p>}
</form>
);
}
2. use() Hook: Conditional Data Loading
use() is React's first Hook that can be called inside conditionals and loops.
Reading Promises
function UserProfile({ userPromise }) {
// Can be used in conditionals!
if (showProfile) {
const user = use(userPromise);
return <h1>{user.name}</h1>;
}
return null;
}
Reading Context
function ThemeToggle() {
// Previously required useContext, now can use use()
const theme = use(ThemeContext);
return <button onClick={toggle}>{theme}</button>;
}
Suspense Integration
function UserPage({ userPromise }) {
return (
<Suspense fallback={<Spinner />}>
<UserProfile userPromise={userPromise} />
</Suspense>
);
}
function UserProfile({ userPromise }) {
const user = use(userPromise);
return <div>{user.name}</div>;
}
3. Server Components Officially Stable
Server vs Client Components
// Server Component (default)
// - Renders on server, no JS sent to client
// - Can directly access databases, filesystem
// - Cannot use useState, useEffect, event handlers
async function BlogList() {
const posts = await db.query('SELECT * FROM posts');
return (
<ul>
{posts.map(post => (
<li key={post.id}>
<h2>{post.title}</h2>
<LikeButton postId={post.id} /> {/* Client Component */}
</li>
))}
</ul>
);
}
// Client Component
'use client';
// - Renders on client (can also SSR)
// - Can use all Hooks
// - Bundled and sent to client
function LikeButton({ postId }) {
const [liked, setLiked] = useState(false);
return <button onClick={() => setLiked(!liked)}>❤️</button>;
}
Server Actions
'use server';
async function createPost(formData) {
const title = formData.get('title');
const content = formData.get('content');
await db.insert('posts', { title, content });
revalidatePath('/blog');
}
// Using Server Action in Client Component
function NewPostForm() {
return (
<form action={createPost}>
<input name="title" />
<textarea name="content" />
<button type="submit">Publish</button>
</form>
);
}
4. React Compiler: Automatic Performance Optimization
React Compiler (formerly React Forget) auto-inserts memo logic, eliminating the need for manual useMemo, useCallback, and memo.
Before Compilation
function ExpensiveComponent({ items, filter }) {
// Developer manually optimizes
const filtered = useMemo(
() => items.filter(i => i.category === filter),
[items, filter]
);
const handleClick = useCallback((id) => {
console.log(id);
}, []);
return <List items={filtered} onClick={handleClick} />;
}
After Compilation (Auto-Optimized)
function ExpensiveComponent({ items, filter }) {
// Write directly, compiler auto-memoizes
const filtered = items.filter(i => i.category === filter);
const handleClick = (id) => console.log(id);
return <List items={filtered} onClick={handleClick} />;
}
Enabling the Compiler
// next.config.js
const nextConfig = {
experimental: {
reactCompiler: true, // or 'unstable' for latest features
},
};
Compiler Limitations
| Non-Optimized Scenarios | Reason |
|---|---|
| Mutating incoming props/mutable objects | Side effects untraceable |
| Closures created outside Hooks | Unclear lifecycle |
Using arguments object |
Non-standard usage |
5. ref as Prop: Goodbye forwardRef
React 18 Approach
const Input = forwardRef((props, ref) => {
return <input ref={ref} {...props} />;
});
React 19 Approach
function Input({ ref, ...props }) {
return <input ref={ref} {...props} />;
}
// Usage
<Input ref={inputRef} placeholder="Enter text" />;
6. Document Metadata
React 19 natively supports <title>, <meta>, <link> declared in components:
function BlogPost({ post }) {
return (
<>
<title>{post.title}</title>
<meta name="description" content={post.excerpt} />
<link rel="canonical" href={`https://example.com/blog/${post.slug}`} />
<article>
<h1>{post.title}</h1>
<p>{post.content}</p>
</article>
</>
);
}
React automatically hoists these tags to <head>, with automatic deduplication and replacement across routes.
7. Other Improvements
useOptimistic: Optimistic Updates
function LikeButton({ postId, initialLiked }) {
const [liked, addOptimisticLike] = useOptimistic(initialLiked);
async function handleLike() {
addOptimisticLike(true); // Immediately show like
await likePost(postId); // Sync in background
}
return <button onClick={handleLike}>{liked ? '❤️' : '🤍'}</button>;
}
useSyncExternalStore Improvements
Supports subscribing to stores where getSnapshot returns different but semantically equivalent values (e.g., cached data fetching).
Full Custom Elements Support
React 19 properly handles Custom Element attributes and events:
<my-custom-element someProp="value" onCustomEvent={handler} />
Migration Checklist
| Change | Impact | Action |
|---|---|---|
forwardRef → ref prop |
All components using forwardRef | Migrate |
useMemo/useCallback |
Manual memo | Gradually remove after enabling Compiler |
onSubmit → action |
Form components | Refactor to Actions |
useContext → use() |
Context consumption | Optional migration |
ref.current.cleanup() |
ref callback cleanup | New pattern |
| String refs deprecated | Legacy code | Migrate to callback/object refs |
Codemod Tool
npx react-codemod@latest react-19
Automatically handles most breaking changes, including forwardRef migration and string ref replacement.