---
name: react-optimizacion
description: Optimización de rendimiento en React. memo, useMemo, useCallback, code splitting, lazy loading, concurrent features, Suspense y patrones de estado eficiente.
version: "1.0.0"
herramientasPermitidas: [Read, Grep]
exclusiones:
  - "No cargar para patrones generales de React (Server Components, data fetching, state management) — para fundamentos cargar `react-experto`."
  - "No cargar para optimización de rendimiento en Angular — las APIs de signals y OnPush son distintas; cargar `angular-moderno` o `angular-avanzado`."
  - "No cargar para optimización CSS o de imágenes (lazy loading de assets) — para CSS cargar `css-moderno` o `tailwind-experto`."
  - "No cargar para profiling de backend o queries SQL — la optimización aquí es exclusivamente del renderizado y bundling del cliente React."
evolvable: true  # default para skill estandar
---
# Optimización de Rendimiento en React

## Cuándo NO cargar

- La tarea es sobre patrones generales de React: Server Components, data fetching, state management — cargar `react-experto`.
- La optimización es para Angular — las APIs de signals, OnPush y zoneless son distintas; cargar `angular-moderno` o `angular-avanzado`.
- El tema es optimización de CSS, imágenes o assets estáticos — cargar `css-moderno` o `tailwind-experto`.
- El cuello de botella es el backend o queries SQL, no el renderizado del cliente — este skill es exclusivamente de rendimiento React en el cliente.

## Principio de optimización prematura

**No optimices antes de medir.** La mayoría de los problemas de rendimiento en React
provienen de re-renders innecesarios o de carga inicial pesada. Primero identifica
el cuello de botella con React DevTools Profiler, luego aplica la solución correcta.

---

## Re-renders: cuándo ocurren y cómo controlarlos

Un componente se re-renderiza cuando:
1. Su propio estado cambia (`useState`, `useReducer`).
2. Su componente padre se re-renderiza (incluso si las props no cambiaron).
3. Un contexto del que consume cambia.

### React.memo — evitar re-renders por props iguales

```tsx
import React, { memo } from 'react';

const FacturaCard = memo(function FacturaCard({
  factura,
  onCancelar,
}: {
  factura: Factura;
  onCancelar: (id: string) => void;
}) {
  return (
    <div className="border rounded-lg p-4">
      <h3>{factura.folio}</h3>
      <button onClick={() => onCancelar(factura.id)}>Cancelar</button>
    </div>
  );
});
```

### useMemo — memorizar cálculos costosos

```tsx
const estadisticas = useMemo(() => ({
  total: facturas.reduce((sum, f) => sum + f.monto, 0),
  emitidas: facturas.filter(f => f.estatus === 'emitida').length,
}), [facturas]);
```

### useCallback — estabilizar referencias de funciones

```tsx
// Sin useCallback, nueva referencia en cada render → memo del hijo no funciona
const handleCancelar = useCallback((id: string) => {
  setFacturas(prev => prev.map(f =>
    f.id === id ? { ...f, estatus: 'cancelada' } : f
  ));
}, []); // Sin dependencias porque usa setter funcional
```

---

## Code Splitting y Lazy Loading

### React.lazy + Suspense

```tsx
import React, { lazy, Suspense } from 'react';
import { Routes, Route } from 'react-router-dom';

const FacturasPage = lazy(() => import('./pages/FacturasPage'));
const ReportesPage = lazy(() => import('./pages/ReportesPage'));

function App() {
  return (
    <Suspense fallback={<PageLoader />}>
      <Routes>
        <Route path="/facturas" element={<FacturasPage />} />
        <Route path="/reportes" element={<ReportesPage />} />
      </Routes>
    </Suspense>
  );
}
```

### Lazy loading de componentes pesados

```tsx
const GraficaVentas = lazy(() => import('./GraficaVentas'));

function Dashboard() {
  const [mostrarGrafica, setMostrarGrafica] = useState(false);
  return (
    <div>
      <button onClick={() => setMostrarGrafica(true)}>Ver gráfica</button>
      {mostrarGrafica && (
        <Suspense fallback={<div className="h-64 animate-pulse bg-gray-100" />}>
          <GraficaVentas />
        </Suspense>
      )}
    </div>
  );
}
```

---

## Concurrent Features — resumen

- **useTransition**: marca actualizaciones como no urgentes. El input responde inmediatamente mientras los resultados se calculan en segundo plano.
- **useDeferredValue**: crea una versión diferida de un valor para mantener el UI fluido.

Para ejemplos completos de useTransition, useDeferredValue, virtualización y optimización de Context, ver [recursos/patrones-avanzados.md](recursos/patrones-avanzados.md).

---

## Virtualización para listas largas

Para listas de >100 elementos, usar `@tanstack/react-virtual` para renderizar solo los elementos visibles. Esto reduce el DOM de miles de nodos a decenas.

Para implementación completa, ver [recursos/patrones-avanzados.md](recursos/patrones-avanzados.md).

---

## Optimización de Context

El contexto causa re-render en TODOS los consumidores cuando cambia. Reglas:
- **Separar** contextos por dominio y frecuencia de cambio (Auth, UI, Datos).
- **Estabilizar** el valor con `useMemo` para evitar objetos nuevos en cada render.
- NUNCA un solo contexto monolítico con todo el estado de la app.

Para ejemplo completo de AuthProvider optimizado, ver [recursos/patrones-avanzados.md](recursos/patrones-avanzados.md).

---

## Cuándo NO usar memo/useMemo/useCallback

Estas optimizaciones tienen un costo: complejidad de código y overhead de comparación.
No aplicar cuando:

- El componente es simple y rápido de renderizar.
- Las props cambian en casi todos los re-renders de todas formas.
- El cálculo en `useMemo` es trivial (suma de 3 números).
- No hay evidencia medida de problema de rendimiento.

**Regla práctica**: medir primero con React DevTools Profiler. Si un componente
aparece en llamas largas o con muchos re-renders no esperados, entonces optimizar.

## Gotchas / Errores comunes no obvios

**`React.memo` no previene re-renders si alguna prop es una función o array creado inline**: `<Lista onSeleccionar={() => handleSelect(id)} />` crea una nueva referencia de función en cada render del padre — `memo` compara con `===` y siempre detecta cambio. Causa: las funciones y objetos literales tienen identidad diferente en cada render. Fix: estabilizar con `useCallback` la función y con `useMemo` los arrays/objetos antes de pasarlos como props a componentes memoizados.

**`useMemo` con dependencias que incluyen objetos del estado no evita recálculos porque el objeto es siempre nuevo**: `useMemo(() => calcular(filtros), [filtros])` recalcula en cada render si `filtros` es `{ texto: '', page: 1 }` definido en el render como literal. Causa: el objeto es una referencia nueva aunque sus valores sean iguales. Fix: usar propiedades primitivas como dependencias (`[filtros.texto, filtros.page]`) en lugar del objeto completo.

**`useTransition` marca la actualización como diferida pero no previene el bloqueo del hilo principal si el cálculo es síncrono costoso**: `startTransition(() => setFiltros(nuevoFiltro))` pospone la actualización de UI pero si el componente resultante tarda 200ms en renderizarse síncronamente, el hilo principal sigue bloqueado. Causa: `useTransition` prioriza, no paraleliza — React aún renderiza síncronamente. Fix: combinar con `useDeferredValue` para el valor que dispara el cálculo costoso, o mover el cálculo a un Web Worker.

**Virtualización con `@tanstack/react-virtual` falla cuando el contenedor no tiene altura fija**: el virtualizador necesita saber el tamaño del contenedor para calcular qué items son visibles — si el contenedor crece con el contenido (sin `height` o `overflow: auto`), estima incorrectamente y renderiza demasiados o muy pocos items. Causa: `useVirtualizer` lee `scrollElement.clientHeight` para calcular el viewport. Fix: dar al contenedor una altura fija (`height: 400px`) o relativa (`height: 100%` con el padre acotado) y `overflow: auto`.
