React memo, useMemo y useCallback (con refs, portales y useId)
React memo, useMemo, useCallback, forwardRef, portales y useId
React memo vs useMemo vs useCallback, forwardRef, portales, useId | We.Inc
Cuándo usar memo, useMemo y useCallback en React, con contadores de renderizado en vivo. Además, forwardRef y ref como prop en React 19, modales con createPortal, etiquetas con useId y una lista virtual de 10,000 filas.
Envuelve un componente en memo para saltarte renderizados cuando sus props no cambian. Usa useCallback para mantener igual una prop de función entre renderizados, y useMemo para cachear un objeto o un cálculo lento. Solo importan junto con memo o cuando hay un coste real. Mide primero con el React Profiler; los contadores en vivo de abajo muestran la diferencia.
Una línea para cada uno
API
Cachea
memo(Component)
El resultado del renderizado de un componente
useMemo(fn, deps)
Un valor
useCallback(fn, deps)
Una función
createPortal(node, el)
Nada; renderiza en otro lugar del DOM
useId()
Nada; crea un id único y estable
Las demos cuentan renderizados reales en tu navegador. Escribe en cada caja con el interruptor apagado y encendido, y observa los contadores. La segunda mitad cubre las APIs de referencia que aparecen junto al trabajo de rendimiento: refs, portales e ids.
Encuentra la interacción lenta con el Profiler de React DevTools.
Consulta la tabla de decisión para el síntoma que ves.
Aplica un solo cambio, como memo en el hijo que se vuelve a renderizar.
Estabiliza las props que recibe con useCallback o useMemo.
Vuelve a perfilar y conserva el cambio solo si los números mejoraron.
memo compara las props de forma superficial. Propagar un objeto nuevo en las props en cada renderizado lo anula.
Baja el estado al componente que lo usa; a menudo eso elimina el renderizado extra sin necesidad de memo.
Pasa children como prop. Que un padre se vuelva a renderizar no vuelve a renderizar los elementos hijos que recibió desde arriba.
React Compiler puede añadir esta memoización por ti en tiempo de compilación. Si tu proyecto lo usa, escribe código normal y deja que se encargue del resto.
Virtualiza las listas a partir de unos cientos de filas complejas y mantén fija la altura de cada fila cuando puedas.
Preguntas frecuentes
¿Qué diferencia hay entre useMemo y useCallback?
useMemo invoca tu función y cachea el valor que devuelve. useCallback cachea la propia función. useCallback(fn, deps) equivale a useMemo(() => fn, deps).
¿Debo envolver todos los componentes en memo?
No. Comparar props también cuesta tiempo, y la mayoría de los renderizados son baratos. Usa memo donde el Profiler muestre un componente que se renderiza a menudo con las mismas props y tarda un tiempo apreciable.
¿Está obsoleto forwardRef?
En React 19, los componentes de función aceptan ref como una prop normal, así que el código nuevo no necesita forwardRef. Sigue funcionando, y el equipo de React ha dicho que quedará obsoleto en una versión futura.
¿Por qué usar un portal para un modal?
Un modal dentro de una tarjeta con overflow hidden o un transform puede quedar recortado o apilado debajo de otro contenido. Renderizarlo en document.body con createPortal evita ambas cosas y lo mantiene en tu árbol de React.
¿Puedo usar useId para las keys de una lista?
No. useId sirve para atributos de accesibilidad como htmlFor y aria-describedby. Las keys deben venir de tus datos, por ejemplo un id de base de datos.
Cómo medir
Abre React DevTools, ve a la pestaña Profiler, graba mientras haces la acción lenta y detén la grabación. El gráfico de llamas muestra qué componentes se renderizaron y cuánto tardó cada uno. Activa "Highlight updates when components render" para ver los renderizados mientras haces clic.
Prueba con una compilación de producción. El modo de desarrollo añade comprobaciones que lo ralentizan todo, y StrictMode renderiza los componentes dos veces a propósito.
Errores habituales
Poner como dependencia un objeto que se recrea en cada renderizado, de modo que useMemo vuelve a calcular cada vez de todas formas.
Omitir una dependencia para evitar un recálculo, lo que muestra datos obsoletos. La regla de lint react-hooks/exhaustive-deps lo detecta.
Guardar el nodo de destino de un portal en un estado que no está listo en el primer renderizado, así que el modal parpadea o lanza un error en el servidor.
Tabla de decisión: memo, useMemo, useCallback u otra cosa
Qué herramienta para qué problema
Síntoma
Usa
Por qué
Un hijo se vuelve a renderizar cuando lo hace su padre, aunque sus props no cambiaron
memo(Child)
Se salta el renderizado cuando todas las props son iguales (Object.is)
Un hijo con memo se sigue renderizando porque le pasas una función nueva cada vez
useCallback
Mantiene la misma función entre renderizados
Un hijo con memo se sigue renderizando porque le pasas un objeto o array nuevo
useMemo
Mantiene el mismo objeto hasta que cambian sus entradas
Un cálculo lento se ejecuta con cada pulsación
useMemo
Cachea el resultado hasta que cambian sus entradas
Miles de filas hacen lento el desplazamiento o la escritura
Una lista virtual (react-window)
Solo las filas visibles están en el DOM
Escribir va con retraso porque una lista grande se actualiza a la vez
useDeferredValue o startTransition
Deja que el campo se actualice primero y la lista se ponga al día después
Nada va visiblemente lento
Nada
Memoizar también tiene un coste; mide primero
Contadores de renderizado, antes y después
memo: saltarse renderizados cuando las props son iguales
Escribe en la caja. Sin memo, la lista se vuelve a renderizar con cada tecla aunque sus props no cambiaron. Con memo se renderiza una vez.
useCallback: mantener la misma función
Pasar una función flecha en línea rompe memo, porque se crea una función nueva en cada renderizado. useCallback devuelve la misma hasta que cambian sus dependencias.
useMemo: cachear un cálculo lento
Filtrar 20,000 productos en cada renderizado es trabajo desperdiciado cuando solo cambió un valor no relacionado. useMemo vuelve a ejecutar el filtro solo cuando cambian los productos o la búsqueda.
forwardRef, ref como prop, portales y useId
forwardRef (React 18 y anteriores)
Un padre no puede pasar ref a un componente de función a menos que el componente la reenvíe. forwardRef pasa la ref hasta un elemento real.
ref como prop (React 19)
Desde React 19, los componentes de función reciben ref como cualquier otra prop, así que forwardRef ya no hace falta en código nuevo.
createPortal para modales y tooltips
Un portal renderiza los hijos en otro nodo del DOM, normalmente document.body, para que un modal escape de padres con overflow hidden o un z-index bajo. Los eventos de React siguen propagándose por el árbol de React.
useId para parejas de etiqueta y campo
useId da un id único que coincide entre el renderizado del servidor y el del navegador, así que varias copias del mismo componente de campo nunca chocan.
Listas largas
react-window: una lista de 10,000 filas
List renderiza solo las filas visibles y unas pocas más. Cada fila recibe index y style; style posiciona la fila y debe aplicarse. La demo de al lado usa la misma idea, escrita a mano.
¿Creas formularios? useId combina bien con
React Hook Form
Lista renderizada {n} veces ({count} elementos)
Envolver la lista en memo
Escribe aquí
({n} renderizados)
Usar useCallback
Pequeño
Mediano
Grande
Elegido: {x}
Cachear con useMemo
Filtrar productos
Clic no relacionado ({n})
{matches} de 20,000 coinciden. El filtro se ejecutó {runs} veces; la última tardó {ms} ms.
Buscar
Enfocar la búsqueda
Esta caja tiene overflow hidden y, aun así, el modal cubre toda la ventana.
Abrir modal
Demo de portal
Me renderizaron en document.body con createPortal.
Cerrar
Email
Respondemos en un día.
Email (otra vez)
Mismo componente, id distinto.
10,000 clientes
Cliente {n}
10,000 filas, pero ahora mismo solo hay {n} en el DOM.