and what really changes
React 19 is not an incremental update. Server Components leave the experimental phase, Server Actions become part of the core API, the React Compiler takes over automatic memoization, and new hooks fundamentally simplify forms, optimistic updates and asset loading. What is relevant for existing projects, and what actually changes in everyday work.
Table of contents
- 1. Placing React 19: evolution or revolution?
- 2. Server Components: what they really can and cannot do
- 3. Server Actions: forms without API routes
- 4. New hooks: useActionState, useFormStatus, useOptimistic
- 5. React Compiler: automatic memoization without useMemo
- 6. Asset loading and resource preloading in React 19
- 7. Breaking changes and deprecations in React 19
- 8. Migrating from React 18 to React 19
- 9. React 19 features compared directly to React 18
- 10. Summary
- 11. FAQ
1. Placing React 19: evolution or revolution?
React 19 is the biggest release since React 18 and the Concurrent Features, and it is both: evolution in API stabilization and revolution in the programming model. What was introduced as experimental in React 18 is stable in React 19: Server Components, streaming and the concurrent renderer. What React 19 newly adds fundamentally changes the relationship between client and server. Instead of loading all data from the client via fetch and managing asynchronous state with useState, components can render directly on the server and access databases, file systems and internal APIs.
The misunderstanding in the community: React 19 does not change how you write simple client components. useState, useEffect and the familiar patterns remain. What changes is the overall picture: whoever uses Server Components and Server Actions writes significantly less boilerplate for data fetching, loading state and error handling. Whoever enables the React Compiler can skip many manual useMemo and useCallback calls. Whoever stays on React 18 loses nothing, but misses out on the biggest productivity gain since the introduction of hooks.
2. Server Components: what they really can and cannot do
Server Components in React 19 run on the server and send only the rendered HTML and a serialized representation of the component tree to the client, no JavaScript bundle for the component itself. That means direct database queries, access to file systems, use of private API keys and calling server-side services without a dedicated API route in between. The result is a smaller JavaScript bundle on the client and significantly simpler code: no useEffect for data fetching, no loading state for initial data, no error handling for a separate API call.
What Server Components in React 19 cannot do: use hooks, have event handlers, call browser APIs or manage interactive state. They are static with regard to interactivity. That leads to the hybrid model: Server Components for data fetching and initial rendering, Client Components (with 'use client') for interactivity. Client Components can be rendered as children of Server Components, receive their props from the server and then manage their own state on the client. The pattern sounds complex but is clear in practice: everything that does not need interaction becomes a Server Component.
// Server Component: runs only on the server, no JS bundle sent to client
// Direct database access without an API route in between
import { db } from '@/lib/database';
async function ProductList({ categoryId }: { categoryId: string }) {
// Direct DB query in a Server Component: no useEffect, no loading state
const products = await db.query(
'SELECT id, name, price FROM products WHERE category_id = $1',
[categoryId]
);
return (
<ul>
{products.rows.map(product => (
// Client Component can be a child of Server Component
<li key={product.id}>
<span>{product.name}, {product.price}€</span>
<AddToCartButton productId={product.id} /> {/* Client Component */}
</li>
))}
</ul>
);
}
// Client Component: interactive, has event handlers, uses hooks
'use client';
import { useState } from 'react';
function AddToCartButton({ productId }: { productId: string }) {
const [added, setAdded] = useState(false);
return (
<button
onClick={() => setAdded(true)}
disabled={added}
>
{added ? 'In cart' : 'Add to cart'}
</button>
);
}
3. Server Actions: forms without API routes
Server Actions are functions marked with 'use server' in React 19 that execute on the server even though they are called from client code. That eliminates one of the most common boilerplate areas in React applications: a form submission no longer needs a separate API route, no fetch call on the client and no manual serialization of the form data. The Server Action receives a FormData object directly, runs the server-side logic and optionally returns a new state.
The decisive advantage of Server Actions in React 19 is progressive enhancement: a form with a Server Action as its action attribute also works without JavaScript, the browser sends the form as a normal POST request. When JavaScript is loaded, React takes over and runs the action without a full page reload. That combines the simplicity of classic HTML form handling with the interactivity of modern SPAs and makes the result simultaneously more accessible and more resilient to JavaScript errors.
4. New hooks: useActionState, useFormStatus, useOptimistic
useActionState is the central new React 19 hook for working with Server Actions. It accepts an action function and an initial state and returns the current state, a wrapped action and an isPending boolean. The wrapped action is passed to the form as the action attribute. The state is updated after every execution of the action and contains both success data and error messages, depending on what the Server Action returns. This fully replaces the previous pattern of useState for loading and error state plus a manual onSubmit handler.
useFormStatus reads the pending state of the surrounding form from a child component. That makes it possible to disable a submit button or show a loading indicator without explicitly passing the state down. useOptimistic immediately shows an optimistic value before the Server Action completes, and restores the previous value if the action fails. Together, these three React 19 hooks build the complete pattern for interactive, resilient forms, with less code than any previous solution.
'use server';
// Server Action: runs on the server, called from the client
import { revalidatePath } from 'next/cache';
import { db } from '@/lib/database';
type ActionState = { success: boolean; message: string } | null;
async function createProduct(prevState: ActionState, formData: FormData): Promise<ActionState> {
const name = formData.get('name') as string;
const price = parseFloat(formData.get('price') as string);
if (!name || isNaN(price)) {
return { success: false, message: 'Name and price are required.' };
}
try {
await db.query('INSERT INTO products (name, price) VALUES ($1, $2)', [name, price]);
revalidatePath('/products');
return { success: true, message: `Product "${name}" was created.` };
} catch {
return { success: false, message: 'Database error while creating the product.' };
}
}
// Client Component using the Server Action via useActionState
'use client';
import { useActionState } from 'react';
function CreateProductForm() {
const [state, action, isPending] = useActionState(createProduct, null);
return (
<form action={action}>
<input name="name" required placeholder="Product name" />
<input name="price" type="number" step="0.01" required placeholder="Price" />
<SubmitButton isPending={isPending} />
{state && (
<p style={{ color: state.success ? 'green' : 'red' }}>{state.message}</p>
)}
</form>
);
}
// useFormStatus reads the pending state of the surrounding form
import { useFormStatus } from 'react-dom';
function SubmitButton({ isPending }: { isPending: boolean }) {
const { pending } = useFormStatus();
return (
<button type="submit" disabled={pending || isPending}>
{pending ? 'Saving...' : 'Create product'}
</button>
);
}
5. React Compiler: automatic memoization without useMemo
The React Compiler is one of the most discussed innovations in React 19. It analyzes React component code at build time and automatically inserts memoization calls where they make semantic sense. The goal: developers should no longer have to worry about whether a value needs to be cached with useMemo or a function stabilized with useCallback. The compiler detects which values can change between renders and optimizes accordingly.
Important for understanding React 19: the React Compiler is an opt-in, not a mandatory part of the framework. It requires valid, rules-compliant React code, so no direct mutations of state, no conditions before hook calls, no side effects during rendering. Existing codebases may need to be refactored before the compiler can be fully enabled. Gradual activation via eslint-plugin-react-compiler is the recommended path: first fix lint errors, then enable the compiler. The benefit is substantial: less manual memoization, better performance by default, shorter codebases.
6. Asset loading and resource preloading in React 19
React 19 brings new APIs for preloading assets directly from React components: preload(), prefetchDNS(), preconnect() and preinit() from react-dom. These functions generate the corresponding link tags in the document head, even when called deep within a component tree. This makes it possible to request resources exactly where they are needed in the component, without manually manipulating the head element or using a library such as react-helmet.
For stylesheet loading in React 19 there is an elegant new option: stylesheets can be imported directly into components and declared with a precedence. React ensures the stylesheets are inserted into the head in the correct order and without duplicates. That also applies to server-rendered stylesheets, which are automatically inserted into the right part of the document during streaming. The combination of asset preloading and stylesheet management makes React 19 a more complete framework for resource management without relying on external libraries.
7. Breaking changes and deprecations in React 19
React 19 removes several APIs that had been marked deprecated since React 18. ReactDOM.render() and ReactDOM.hydrate() have been replaced by ReactDOM.createRoot() and ReactDOM.hydrateRoot(), whoever still uses the old APIs must migrate. The Legacy Context API (contextTypes, childContextTypes, getChildContext) is fully removed in React 19; the replacement is the modern createContext API. findDOMNode and string refs are also among the removed APIs. Whoever still uses these must migrate before upgrading to React 19.
On the side of behavioral changes: in React 19, errors in error handling (error boundaries, onRecoverableError) are no longer logged twice. That simplifies debugging. ref is a regular prop in React 19, which makes forwardRef redundant and deprecated. Function components no longer receive ref directly as a second argument, but as part of the props object. That is a breaking change for all components that use forwardRef, but the migration path is straightforward: remove forwardRef and read ref directly from props.
// React 18: forwardRef required to pass ref to function components
import { forwardRef } from 'react';
const Input = forwardRef<HTMLInputElement, { label: string }>(
({ label }, ref) => (
<div>
<label>{label}</label>
<input ref={ref} />
</div>
)
);
// React 19: ref is a regular prop, no forwardRef needed
function Input({ label, ref }: { label: string; ref?: React.Ref<HTMLInputElement> }) {
return (
<div>
<label>{label}</label>
<input ref={ref} />
</div>
);
}
// React 18: ReactDOM.render (removed in React 19)
// ReactDOM.render(<App />, document.getElementById('root'));
// React 19: createRoot, already required since React 18, now the only option
import { createRoot } from 'react-dom/client';
const root = createRoot(document.getElementById('root')!);
root.render(<App />);
// React 19: ref cleanup function in useEffect style
function VideoPlayer({ src }: { src: string }) {
return (
<video
src={src}
ref={(node) => {
if (node) {
// Setup: ref attached
node.play();
// Cleanup function returned from ref callback
return () => node.pause();
}
}}
/>
);
}
8. Migrating from React 18 to React 19
The migration to React 19 starts with the codemod: npx react-codemod@latest cra-to-react-19, but this tool does not cover all cases and should only serve as a starting point. The most important manual step: identify all forwardRef usages and migrate them to the new ref-as-prop model. Then replace the Legacy Context API, ReactDOM.render and findDOMNode. TypeScript users must update to @types/react@19, since the type definitions for ref, children and many prop interfaces have changed.
Server Components and Server Actions are not a mandatory part of React 19, they require framework support (Next.js 15+, Remix, TanStack Start). A pure React 19 app without a framework cannot use Server Components. The React Compiler is also optional and requires separate configuration. The pragmatic migration approach: first fix the breaking changes, then evaluate the new features as a next step. Completing a full React 19 migration in a week is unrealistic for larger codebases, a gradual approach with feature flags and parallel development is safer.
9. React 19 features compared directly to React 18
The biggest question about React 19 is not "what is new", but "what changes in everyday work". This comparison shows the practical differences for typical tasks.
| Task | React 18 | React 19 | Benefit |
|---|---|---|---|
| Data fetching | useEffect + fetch + useState | Server Component with async/await | No loading state, smaller bundle |
| Form submit | onSubmit + fetch + useState for errors | Server Action + useActionState | No API route, less boilerplate |
| Memoization | Manual useMemo / useCallback | React Compiler automatic | Less code, more consistent optimization |
| Passing ref | forwardRef() wrapping | ref as a regular prop | Less boilerplate, simpler code |
| Optimistic updates | Manual with useState rollback logic | useOptimistic hook | Automatic rollback on error |
| Asset preloading | react-helmet or manual manipulation | preload(), preinit() from react-dom | No external library needed |
The table shows: React 19 primarily simplifies the areas that still required a lot of boilerplate in React 18. Data fetching, form handling and memoization are the three biggest productivity problems in React 18 codebases, and all three are fundamentally simplified in React 19. Whoever wants to use these features needs framework support for Server Components and Server Actions.
Mironsoft
React migration, Server Components and modern frontend architecture
React 19 migration for your project?
We analyze existing React 18 codebases for migration paths to React 19, identify breaking changes and support the gradual introduction of Server Components, Server Actions and the React Compiler.
Migration audit
Codebase analysis for breaking changes, forwardRef, Legacy Context and deprecated APIs
Server Components
Architecture planning for Server Components and Server Actions in Next.js 15 or Remix
React Compiler
Gradual activation of the React Compiler with ESLint plugin and codebase cleanup
10. Summary
React 19 is a release that extends the component model with a server dimension while simultaneously addressing existing pain points through new hooks and the React Compiler. Server Components enable direct database access without API routes. Server Actions simplify form handling to a level that previously required complex libraries. useActionState, useFormStatus and useOptimistic are the matching client-side hooks for that. The React Compiler takes over automatic memoization and significantly reduces manual optimization overhead.
The breaking changes in React 19 are real but manageable: replace forwardRef with the new ref-as-prop model, migrate Legacy Context APIs, replace ReactDOM.render with createRoot. Whoever uses TypeScript must account for the updated types. The migration effort pays off: React 19 is the foundation for the coming years of React ecosystem development, and all major frameworks and libraries are orienting themselves around its new primitives.
React 19: the essentials at a glance
Server features
Server Components for direct data fetching without API routes. Server Actions for form handling without onSubmit + fetch. Framework support (Next.js 15+) required.
New hooks
useActionState connects actions with state. useFormStatus reads pending state from child components. useOptimistic for automatic rollback on errors.
Breaking changes
forwardRef deprecated, ref is a regular prop. ReactDOM.render removed. Legacy Context API removed. TypeScript types changed, update @types/react@19.
React Compiler
Optional opt-in for automatic memoization. Requires rules-compliant React code. Gradual activation with eslint-plugin-react-compiler recommended.