React 19: Las Novedades que Cambian tu Código en 2026
Actions, use(), useOptimistic, Server Components estables y el compilador de React. Qué cambia de verdad en React 19 con ejemplos de código reales y cómo migrar sin romper nada.
Tabla de contenidos
🎯 Lo que aprenderás en este artículo
- Las Actions sustituyen el patrón useState + event handler para formularios asíncronos
- useOptimistic actualiza la UI antes de que el servidor confirme, sin código extra
- El hook use() permite leer Promises y Context en medio de un render
- ref ya no necesita forwardRef: se pasa como prop directamente
- El compilador de React (React Compiler) memoiza automáticamente — adiós a useMemo y useCallback manuales
React 19 salió en diciembre de 2024. Si usas Next.js 15, ya lo tienes. Si sigues en React 18 con Vite, probablemente aún no has migrado porque “nada está roto”.
Aquí van las novedades que de verdad cambian cómo escribes código, con ejemplos antes/después para que veas el impacto real.
1. Actions: adiós al patrón isPending manual
El patrón más repetido en React: formulario asíncrono con estado de carga y manejo de errores.
React 18 — el patrón clásico:
function FormularioContacto() {
const [isPending, setIsPending] = useState(false);
const [error, setError] = useState<string | null>(null);
const [success, setSuccess] = useState(false);
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
setIsPending(true);
setError(null);
try {
await enviarFormulario(new FormData(e.target as HTMLFormElement));
setSuccess(true);
} catch (err) {
setError('Error al enviar. Inténtalo de nuevo.');
} finally {
setIsPending(false);
}
}
return (
<form onSubmit={handleSubmit}>
{/* campos del formulario */}
<button disabled={isPending}>{isPending ? 'Enviando...' : 'Enviar'}</button>
{error && <p className="text-red-500">{error}</p>}
</form>
);
}
React 19 — con Actions y useActionState:
import { useActionState } from 'react';
async function enviarAction(prevState: any, formData: FormData) {
try {
await enviarFormulario(formData);
return { success: true, error: null };
} catch {
return { success: false, error: 'Error al enviar. Inténtalo de nuevo.' };
}
}
function FormularioContacto() {
const [state, action, isPending] = useActionState(enviarAction, {
success: false,
error: null,
});
return (
<form action={action}>
{/* campos del formulario */}
<button disabled={isPending}>{isPending ? 'Enviando...' : 'Enviar'}</button>
{state.error && <p className="text-red-500">{state.error}</p>}
</form>
);
}
El isPending, el try/catch y el reset de estado lo gestiona React. Tu código se encarga solo de la lógica de negocio.
2. useOptimistic: UI instantánea antes de que el servidor responda
Uno de los patrones más útiles para UX en apps con servidor. Antes necesitabas hacerlo a mano.
Caso de uso: lista de comentarios donde el nuevo comentario aparece al instante al enviarlo, antes de que el servidor confirme.
import { useOptimistic, useActionState } from 'react';
function ListaComentarios({ comentariosIniciales }: { comentariosIniciales: Comentario[] }) {
const [comentarios, addOptimistic] = useOptimistic(
comentariosIniciales,
(state, nuevoComentario: string) => [
...state,
{ id: Date.now(), texto: nuevoComentario, pendiente: true },
]
);
const [, action, isPending] = useActionState(async (_: any, formData: FormData) => {
const texto = formData.get('comentario') as string;
addOptimistic(texto); // UI actualiza INSTANTÁNEAMENTE
await guardarComentario(texto); // esto puede tardar 300ms
}, null);
return (
<div>
{comentarios.map((c) => (
<div key={c.id} className={c.pendiente ? 'opacity-60' : ''}>
{c.texto}
</div>
))}
<form action={action}>
<input name="comentario" />
<button disabled={isPending}>Comentar</button>
</form>
</div>
);
}
El comentario aparece en la lista inmediatamente. Si el servidor falla, React hace rollback automático.
3. El hook use(): Promises y Context en medio de un render
use() es el primer hook que puedes llamar dentro de condicionales (rompiendo la regla de siempre).
Leer una Promise (con Suspense):
import { use, Suspense } from 'react';
function Usuario({ promesaUsuario }: { promesaUsuario: Promise<User> }) {
// Esto se puede llamar dentro de un if, un bucle, etc.
const usuario = use(promesaUsuario);
return <p>Hola, {usuario.nombre}</p>;
}
function App() {
const promesa = fetchUsuario(1); // Promise<User>
return (
<Suspense fallback={<p>Cargando...</p>}>
<Usuario promesaUsuario={promesa} />
</Suspense>
);
}
Leer Context de forma condicional:
function Boton({ mostrarAdmin }: { mostrarAdmin: boolean }) {
if (mostrarAdmin) {
// Antes esto era imposible — los hooks no se pueden usar en condicionales
const admin = use(AdminContext);
return <button onClick={admin.logout}>Cerrar sesión</button>;
}
return <button>Entrar</button>;
}
4. ref como prop — adiós a forwardRef
Uno de los cambios más celebrados. Antes, para pasar una ref a un componente hijo necesitabas forwardRef:
React 18:
const Input = forwardRef<HTMLInputElement, InputProps>((props, ref) => (
<input ref={ref} {...props} />
));
React 19:
function Input({ ref, ...props }: InputProps & { ref?: React.Ref<HTMLInputElement> }) {
return <input ref={ref} {...props} />;
}
// Uso igual que antes:
const inputRef = useRef<HTMLInputElement>(null);
<Input ref={inputRef} />
forwardRef sigue funcionando pero está deprecado. La migración es mecánica.
5. El React Compiler: memoización automática
Esta es la novedad más grande en el largo plazo. El compilador analiza tu código en build time y añade useMemo y useCallback automáticamente donde es necesario.
Antes (React 18 con optimización manual):
function ProductoCard({ producto, onAgregar }: Props) {
const precioFormateado = useMemo(
() => new Intl.NumberFormat('es-ES', { style: 'currency', currency: 'EUR' }).format(producto.precio),
[producto.precio]
);
const handleAgregar = useCallback(() => {
onAgregar(producto.id);
}, [onAgregar, producto.id]);
return (
<div>
<p>{producto.nombre}</p>
<p>{precioFormateado}</p>
<button onClick={handleAgregar}>Agregar</button>
</div>
);
}
Con React Compiler:
function ProductoCard({ producto, onAgregar }: Props) {
// Sin useMemo ni useCallback — el compilador los añade solo
const precioFormateado = new Intl.NumberFormat('es-ES', {
style: 'currency', currency: 'EUR'
}).format(producto.precio);
return (
<div>
<p>{producto.nombre}</p>
<p>{precioFormateado}</p>
<button onClick={() => onAgregar(producto.id)}>Agregar</button>
</div>
);
}
El output compilado incluye la memoización, el código fuente no. Limpio y eficiente.
Para activarlo en Vite:
npm install babel-plugin-react-compiler
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [
react({
babel: {
plugins: [['babel-plugin-react-compiler', {}]],
},
}),
],
});
6. Document metadata nativo
Ya no necesitas react-helmet para gestionar <title>, <meta> y <link> en el head:
function PaginaBlog({ post }: { post: Post }) {
return (
<>
<title>{post.titulo} | Mi Blog</title>
<meta name="description" content={post.descripcion} />
<link rel="canonical" href={`https://misite.com/blog/${post.slug}`} />
<article>
<h1>{post.titulo}</h1>
{/* contenido */}
</article>
</>
);
}
React eleva estos elementos al <head> automáticamente, sin portals ni librerías externas.
Cómo migrar desde React 18
npm install react@19 react-dom@19 @types/react@19 @types/react-dom@19
Los cambios que probablemente romperán algo:
ReactDOM.rendereliminado → usaReactDOM.createRootreact-dom/test-utilseliminado → importa de@testing-library/reactdefaultPropsen funciones → usa parámetros por defecto de JSpropTypeseliminado → usa TypeScript
Para el 95% de proyectos modernos (Vite + TypeScript, Next.js 14+), la migración es sin cambios o con 1-2 pequeños ajustes.
Cuándo adoptar cada feature
| Feature | ¿Cuándo usar? |
|---|---|
useActionState | Formularios con lógica asíncrona. Ya. |
useOptimistic | UX de apps tipo chat, likes, listas. |
use() con Promises | Data fetching con Suspense. |
ref como prop | Migra cuando toques esos componentes. |
| React Compiler | Proyectos nuevos. Pruébalo en uno existente. |
| Document metadata | Sustituye react-helmet cuando puedas. |
Recursos relacionados