Volver a React Intermedio

Características Concurrentes en React 18+

React 18 introdujo el renderizado concurrente — la capacidad de interrumpir, pausar y reanudar renders. Estas nuevas APIs desbloquean UIs más fluidas.

useTransition

Marca las actualizaciones de estado como no urgentes para mantener la UI responsiva:

import { useState, useTransition } from 'react'; function PaginaBusqueda() { const [consulta, setConsulta] = useState(''); const [resultados, setResultados] = useState<string[]>([]); const [pendiente, iniciarTransicion] = useTransition(); const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { // Urgente: actualizar el input inmediatamente setConsulta(e.target.value); // No urgente: actualizar resultados puede esperar iniciarTransicion(() => { setResultados(busquedaCostosa(e.target.value)); }); }; return ( <div> <input value={consulta} onChange={handleChange} placeholder="Buscar..." /> {pendiente ? ( <p className="text-gray-400">Buscando...</p> ) : ( <ul> {resultados.map(r => <li key={r}>{r}</li>)} </ul> )} </div> ); }

useDeferredValue

Difiere un valor para evitar bloquear renders urgentes:

import { useDeferredValue, memo } from 'react'; const ListaPesada = memo(({ consulta }: { consulta: string }) => { // Este componente puede ser lento — usa el valor diferido const filtrados = listaGrande.filter(item => item.includes(consulta)); return <ul>{filtrados.map(i => <li key={i}>{i}</li>)}</ul>; }); function App() { const [consulta, setConsulta] = useState(''); const consultaDiferida = useDeferredValue(consulta); return ( <> {/* Input responde de inmediato */} <input value={consulta} onChange={e => setConsulta(e.target.value)} /> {/* Lista usa el valor diferido — renderiza con el valor anterior mientras calcula el nuevo */} <ListaPesada consulta={consultaDiferida} /> </> ); }

Suspense para Obtención de Datos

import { Suspense } from 'react'; // Con una librería compatible con Suspense (SWR, React Query, o Next.js) function PerfilUsuario({ id }: { id: string }) { // Este componente "suspende" hasta que los datos estén listos const usuario = useSuspenseQuery(['usuario', id], () => obtenerUsuario(id)); return <div>{usuario.nombre}</div>; } function App() { return ( <Suspense fallback={<EsqueletoUsuario />}> <ErrorBoundary fallback={<p>Error al cargar usuario</p>}> <PerfilUsuario id="123" /> </ErrorBoundary> </Suspense> ); }

useId

Genera IDs únicos y estables para accesibilidad:

import { useId } from 'react'; function CampoFormulario({ etiqueta, tipo = 'text' }: { etiqueta: string; tipo?: string }) { const id = useId(); return ( <div> <label htmlFor={id}>{etiqueta}</label> <input id={id} type={tipo} aria-describedby={`${id}-ayuda`} /> <span id={`${id}-ayuda`} className="ayuda"> Ingresa tu {etiqueta.toLowerCase()} </span> </div> ); } // Múltiples instancias — cada una recibe un ID único y estable <CampoFormulario etiqueta="Correo" tipo="email" /> // id: :r0: <CampoFormulario etiqueta="Teléfono" tipo="tel" /> // id: :r1:

useLayoutEffect vs useEffect

import { useEffect, useLayoutEffect, useRef } from 'react'; function ComponenteMedido() { const ref = useRef<HTMLDivElement>(null); // useLayoutEffect se ejecuta síncronamente ANTES de que el navegador pinte // Úsalo para leer medidas del DOM y re-renderizar de forma síncrona useLayoutEffect(() => { const altura = ref.current?.offsetHeight; // Ajustar algo basado en la altura — sin parpadeo visual if (altura && altura > 200) { ref.current!.style.overflow = 'auto'; } }, []); // useEffect se ejecuta DESPUÉS de que el navegador pinte (asíncrono) — para la mayoría de efectos useEffect(() => { analiticas.registrar('componente_montado'); }, []); return <div ref={ref}>Contenido</div>; }