Cómo debuggear problemas de renderizado en React (herramientas y técnicas)
Objetivo: detectar por qué un componente renderiza de más, medir el impacto y aplicar correcciones seguras sin romper la experiencia de usuario.
1) Señales de que estás sufriendo renders innecesarios
- Scroll entrecortado, animaciones “lag”.
- Inputs que se sienten “pesados”.
- Uso de CPU alto en pestañas inactivas.
- La vista cambia aunque los datos no.
2) Herramientas imprescindibles
React DevTools
Instala la extensión y abre la pestaña Components para ver qué renderiza y por qué. Activa Highlight updates when components render para resaltar el DOM que se actualiza.
Profiler de React
Desde la pestaña Profiler graba una interacción. Revisa el flamegraph y ordena por “commit duration” para ubicar cuellos de botella. Usa la vista Ranked para localizar los componentes más costosos.
Chrome DevTools: Performance & Memory
- Performance: registra con CPU throttling (x4) para amplificar problemas.
- Coverage: detecta JS/CSS no utilizado que incrementa el trabajo de render.
- Memory: observa snapshots para objects retenidos por closures o refs.
Linters y diagnósticos
eslint-plugin-react-hookspara dependencias correctas.why-did-you-renderpara avisar cuando un componente vuelve a renderizar con las mismas props/estado.
3) Checklist de causas comunes y cómo resolverlas
3.1 Props y estado inestables
Síntoma: pasas objetos/arrays/funciones creadas en cada render.
function Parent({ items }) {
const onSelect = (id) => setSelected(id); // se recrea cada render
return <List items={items} onSelect={onSelect} />;
}
Solución: estabiliza referencias con useCallback y useMemo.
const onSelect = useCallback((id) => setSelected(id), []);
const stableData = useMemo(() => ({ foo: 1 }), []);
3.2 Falta de memoización de componentes puros
Si un componente renderiza con las mismas props, envuélvelo en React.memo:
const Row = React.memo(function Row({ item, onClick }) {
/* ... */
});
Para props complejas, añade areEqual personalizado o normaliza datos antes.
3.3 Estado global que dispara renders masivos
Evita que todo el árbol dependa de un mismo contexto. Divide contextos o usa librerías con selectors (ej.: zustand) para suscribirte a fragmentos.
3.4 Keys inestables
Usar índices como clave en listas puede provocar renders y errores al reordenar. Prefiere IDs estables.
3.5 Efectos que actualizan estado en cada render
useEffect(() => {
setWidth(window.innerWidth); // corre en cada render si depende de algo inestable
});
Añade dependencias correctas o usa useLayoutEffect sólo cuando sea imprescindible.
3.6 Derivar estado de props
Si calculas estado a partir de props en cada render, considera useMemo o useMemo + useEffect sólo cuando la prop cambie.
3.7 Re-render por cambios de estilo
Evita crear objetos de estilo inline nuevos. Usa clases o memoriza los objetos.
4) Patrón de diagnóstico en 7 pasos
- Reproduce el problema con un caso mínimo.
- Mide con el Profiler (baseline de ms por interacción).
- Detecta el componente más caro (flamegraph).
- Compara props/estado entre render previo y actual.
- Estabiliza referencias (
useCallback/useMemo/React.memo). - Vuelve a medir y verifica mejora >= 20–30%.
- Refactoriza zonas calientes (virtualización, pagination, split de context).
5) Técnicas de optimización efectivas
- Virtualización con
react-windoworeact-virtualpara listas largas. - Suspense +
useTransition/useDeferredValuepara suavizar interacciones intensas. - Code-splitting con
React.lazyy bundlers modernos. - Evitar trabajo en render: mueve cálculos pesados a web workers.
- Server Components / SSR para reducir trabajo del cliente cuando aplique.
6) Ejemplos cortos de “antes/después”
Funciones inline vs useCallback
// ❌ antes
<Button onClick={() => doSomething(value)} />
// ✅ después
const handleClick = useCallback(() => doSomething(value), [value]);
<Button onClick={handleClick} />
Objeto de estilos estable
// ✅ memoriza el objeto
const boxStyle = useMemo(() => ({ padding: 8, borderRadius: 8 }), []);
<div style={boxStyle} />
7) Cómo leer el Profiler como pro
- Commit: conjunto de cambios aplicados al DOM virtual.
- Foco en picos: busca commits con duración inusual.
- “Why did this render?”: en DevTools observa cambios de props/estado que dispararon el render.
8) Errores frecuentes y cómo evitarlos
- Dep arrays incompletos en
useEffect→ usa el linter para forzar dependencias correctas. - Context monolítico → separa proveedores por dominio (tema, usuario, permisos).
- Keys inestables en listas → usa IDs reales.
- Recrear selectores/filtrados pesados en cada render → memoiza o precalcula.
9) Utilidades recomendadas
why-did-you-render(diagnóstico de renders redundantes).react-window/react-virtual(virtualización).eslint-plugin-react-hooks(disciplina en hooks).
10) Checklist final (copiar/pegar)
- ¿Tengo funciones/objetos/arrays inline? →
useCallback/useMemo. - ¿Componentes puros? →
React.memo. - ¿Contextos divididos por dominio?
- ¿Keys estables en listas?
- ¿Virtualización para listas > 100 ítems?
- ¿Profiler muestra mejora tras cambios?
- ¿Linters y WDYR activos en dev?
Consejo: optimiza sólo después de medir. La micro-optimización prematura complica el código sin beneficios reales.