Inicio / Artículos / Triaje para la actualización a React 19: ¿Qué nuevas APIs reemplazan las soluciones temporales que tienes?

Triaje para la actualización a React 19: ¿Qué nuevas APIs reemplazan las soluciones temporales que tienes?

Una visión práctica del uso, las Server Actions, useOptimistic y el React Compiler, con las advertencias importantes y un plan para adoptar primero qué cambios en React 19.

1579 palabras

React 19 añadió más funcionalidades de API que cualquier versión en años, y la gran cantidad de documentación hace difícil identificar qué es importante para una base de código existente. Para la mayoría de los equipos, algunas nuevas funciones reemplazan soluciones temporales que ya se utilizan, otra cambia la forma en que se aborda la carga de datos, y el resto puede esperar. A continuación se detallan las que son relevantes, con ejemplos de código antes y después, así como las advertencias fáciles de pasar por alto, para que puedan planificar la actualización según su valor real.

use: leer Promises y Context durante el renderizado

La API use es la adición más significativa, y se comporta de manera diferente a todos los hooks que conoce. Los hooks habituales deben llamarse en el nivel superior de un componente, en el mismo orden en cada renderizado. use está exento de esta regla: se puede llamar después de un retorno anticipado, dentro de una condición o dentro de un bucle.

En el ejemplo a continuación, el componente muestra una vista de invitado cuando no existe un userId, y solo entonces lee los datos del usuario:

// React 19 — use() can be called inside conditionals and loops
function UserProfile({ userId }) {
  if (!userId) return <GuestView />;

  // This is valid in React 19
  const user = use(fetchUser(userId));

  return <div>{user.name}</div>;
}

El verdadero poder radica en lo que acepta use: una Promise o un Context. Cuando se proporciona una Promise, React suspende el componente hasta que esta se resuelva. La comparación a continuación muestra cuánto desaparece en la versión antigua: esta seguía registrando los datos y una bandera de carga en el estado, realizaba la solicitud mediante un efecto y mostraba manualmente un spinner; en cambio, la versión de React 19 lee el valor directamente:

// Before React 19
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetchUser(userId).then(data => {
      setUser(data);
      setLoading(false);
    });
  }, [userId]);

  if (loading) return <Spinner />;
  return <div>{user.name}</div>;
}

// React 19
function UserProfile({ userId }) {
  const user = use(fetchUser(userId));
  return <div>{user.name}</div>;
}

El trabajo no desapareció, sino que se trasladó: el límite más cercano de <Suspense> muestra la vista alternativa mientras la Promise está pendiente, y el límite de errores más cercano se encarga de manejar las rechazos. El componente solo describe el camino de éxito.

La Promise debe ser estable

use espera que se utilice la misma Promise en cada renderizado. Si fetchUser(userId) crea una nueva en cada renderizado, React vuelve a suspenderse en cada ocasión, lo que provoca solicitudes repetidas o un componente que nunca llega a finalizar; React emite advertencias sobre las Promises no almacenadas en caché que se crean durante el renderizado en los componentes de cliente. Por lo tanto, considere estos ejemplos como ilustraciones de la estructura deseada. La Promise debe provenir de algo que la almacene en caché: una biblioteca de datos consciente de Suspense, un cargador de framework o un componente de servidor que inicie la solicitud y transmita la Promise como prop. A veces se sugiere envolver la llamada en useMemo, pero React no garantiza que los valores memorizados se mantengan, por lo que no constituye un caché fiable.

Acciones en el servidor como característica de React

Los usuarios de Next.js App Router ya conocen las Server Actions. En React 19 forman parte del propio React (los documentos ahora las denominan Server Functions) y están disponibles en cualquier framework que soporte los Server Components.

El ejemplo define una función asíncrona marcada con 'use server', que inserta un usuario y vuelve a validar la lista, y la pasa al atributo action de un formulario:

// Server Action — runs on the server, called from the client
async function submitForm(formData) {
  'use server';

  const name = formData.get('name');
  await db.users.create({ name });
  revalidatePath('/users');
}

// Client component
function UserForm() {
  return (
    <form action={submitForm}>
      <input name="name" />
      <button type="submit">Add User</button>
    </form>
  );
}

El atributo action de <form> ahora acepta una función, incluyendo las asíncronas; React la ejecuta durante la transición al enviar el formulario y le pasa los datos de FormData. El estado pendiente, las actualizaciones optimistas y los errores provienen de APIs complementarias: useFormStatus o useActionState para el estado pendiente y los resultados, useOptimistic para obtener retroalimentación instantánea, y los límites de errores para manejar fallos. Usted escribe la mutación; React coordina el resto.

Un detalle del fragmento necesita ajuste en una aplicación real. Una función con la directiva inline 'use server' solo puede definirse en un Componente Servidor. Si UserForm es un Componente Cliente, mueva submitForm a su propio archivo con 'use server' al principio e impórtelo.

Mientras que los Componentes Servidor eliminan la interfaz gráfica estática del paquete del cliente, las Acciones Servidor eliminan la ruta API escrita a mano para las mutaciones: menos código y menos viajes de ida y vuelta. Aún así, trate cada acción como un endpoint público y valide la entrada y la autorización dentro de ella.

useOptimistic: retroalimentación instantánea sin estado duplicado

Antes, renderizar un resultado antes de la confirmación del servidor requería gestionar manualmente un estado adicional. useOptimistic toma el estado actual y una función de actualización similar a un reducer, y devuelve el estado para renderizar junto con una función que aplica el cambio optimista. En este caso, al agregar un tarea se inserta un elemento temporal marcado como pendiente, el cual se muestra ligeramente atenuado hasta que finaliza el guardado:

function TodoList({ todos }) {
  const [optimisticTodos, addOptimisticTodo] = useOptimistic(
    todos,
    (currentTodos, newTodo) => [...currentTodos, newTodo]
  );

  async function handleAdd(text) {
    addOptimisticTodo({ id: 'temp', text, pending: true });
    await saveTodo(text); // Server Action
  }

  return (
    <ul>
      {optimisticTodos.map(todo => (
        <li key={todo.id} style={{ opacity: todo.pending ? 0.7 : 1 }}>
          {todo.text}
        </li>
      ))}
    </ul>
  );
}

Cuando se llama a addOptimisticTodo, React muestra inmediatamente la lista optimista. Cuando finaliza la acción correspondiente, el valor optimista se descarta y React vuelve a renderizar desde la propiedad todos, que para entonces debería contener el elemento guardado.

Antes se conservaban copias confirmadas y pendientes de los datos, y se realizaba la limpieza a mano cuando había discrepancias; ahora el hook se encarga de ese ciclo de vida. Hay dos condiciones importantes. Primero, la actualización optimista debe realizarse dentro de una acción o transición, por ejemplo, una función pasada a la propiedad action de un formulario o envuelta en startTransition; llamarla desde un simple manejador de eventos hace que React emita una advertencia y la actualización podría no mostrarse. Segundo, el padre debe recibir realmente los todos actualizados después del guardado, generalmente mediante revalidación; de lo contrario, el nuevo elemento desaparecerá cuando se restablezca el estado optimista. Los IDs temporales como 'temp' también causan conflictos si se añaden dos elementos rápidamente, por lo que es necesario generar uno único. Abordamos estos casos límite en cinco modos de fallo en useOptimistic rollback.

El compilador de React: memorización por defecto

El compilador de React, anteriormente React Forget, llegó junto con React 19 como un paso de compilación opcional, y tiene el efecto más duradero en el código cotidiano. Inserta la memorización en el momento de la compilación, de modo que los valores y las funciones de callback se reutilizan cuando los datos de entrada no cambian y los hijos evitan volver a renderizarse cuando las propiedades son las mismas. Los usos manuales de useMemo, useCallback y React.memo se vuelven en su mayoría innecesarios, como lo demuestra la comparación siguiente:

// Before: manual memoization required
const expensiveValue = useMemo(() => compute(a, b), [a, b]);
const stableCallback = useCallback(() => doSomething(id), [id]);
const MemoizedChild = React.memo(ChildComponent);

// After React Compiler: write normal code
const expensiveValue = compute(a, b);
const handleClick = () => doSomething(id);
// ChildComponent renders only when its props change - automatically

Aún es necesario entender cuándo y por qué los componentes se vuelven a renderizar, pero las soluciones manuales son mucho menos importantes. Nuestra visión general de patrones que provocan renderizaciones innecesarias sigue siendo útil como información de contexto.

Para proyectos nuevos, el compilador es una opción por defecto adecuada. En código existente, implementarlo con cuidado: asume que los componentes son puros y siguen las Reglas de React, por lo que el código que muta durante la renderización puede comportarse de manera diferente una vez compilado. Actívelo gradualmente, ejecute sus pruebas y utilice el plugin de ESLint para detectar primero las infracciones. Consulte la documentación actual de React para conocer su estado de lanzamiento y las versiones de React compatibles.

Qué adoptar y en qué orden

Valor inmediato, bajo riesgo

  • useOptimistic dondequiera que los formularios interactúen con el estado del servidor.
  • Server Actions si está utilizando Next.js 14 o posterior y desea reemplazar las rutas API para las mutaciones.

Vale la pena planificarlo

  • use una vez que su capa de datos genere Promises estables y en caché; se integra naturalmente con Suspense.
  • El compilador de React en nuevos proyectos, o primero en las partes ya probadas de una aplicación existente.
  • No es urgente para la mayoría de las aplicaciones

    • Cambios en ref: ahora ref puede pasarse como un prop normal a los componentes funcionales, por lo que forwardRef ya no es necesario.
    • Mejoras en el contexto, que son principalmente cambios en la experiencia del desarrollador y no nuevos comportamientos.
    • Soporte para metadatos de documentación, lo que permite a los componentes renderizar las etiquetas <title> y <meta> que React coloca en la cabecera del documento.

    Puntos clave

    • React 19 es una evolución y no una reescritura que rompa funcionalidades. Sus nuevas API formalizan patrones que los equipos de producción ya implementan manualmente: actualizaciones optimistas, mutaciones en el servidor y lectura condicional de datos.
    • use traslada el manejo de la carga y los errores a Suspense y los límites de error, pero solo funciona bien con Promises que se almacenan en caché fuera del proceso de renderizado.
    • Server Actions y useOptimistic combinan de forma natural; el primero se encarga de la escritura, mientras que el segundo oculta su latencia.
    • El compilador establece la memorización como valor por defecto, siempre y cuando tus componentes cumplan con las Reglas de React.

    La pregunta útil no es si actualizar o no, sino cuál de estas herramientas reemplaza la solución temporal que estás utilizando actualmente. Empieza por ahí.

    Lecturas relacionadas

  • Formas asincrónicas en React 19 con use, useActionState y useOptimistic — Cómo use(), useActionState, useFormStatus y useOptimistic reemplazan los indicadores manuales de carga y errores en React 19, junto con las trampas que cada hook oculta silenciosamente.