NUCBA
20 de enero de 2026
programación

Tu componente React renderiza 47 veces por segundo

Los re-renders innecesarios cuestan plata. Tres técnicas para detectarlos y eliminarlos sin reescribir tu aplicación.

Equipo NUCBA

Equipo NUCBA

7 min de lectura

El problema que nadie mide

Abrís el DevTools, activás el Profiler de React y ves algo perturbador: ese formulario simple que acabás de tipear una letra acaba de disparar 23 renders en componentes que no tenían nada que ver. El select del header se renderizó. El footer también. Hasta el modal que está cerrado.

La performance en React no se trata de micro-optimizaciones prematuras. Se trata de entender cuándo un componente se vuelve a ejecutar y por qué. Porque cada render innecesario es trabajo desperdiciado: diff del virtual DOM, comparaciones, efectos que se vuelven a ejecutar, memoria que se asigna y libera.

La buena noticia: la mayoría de los problemas de performance en React tienen tres causas. Y todas son predecibles.

Por qué React renderiza (y cuándo te importa)

React renderiza un componente cuando:

  1. Su estado cambia (useState, useReducer)
  2. Sus props cambian (el padre le pasó algo nuevo)
  3. Su padre renderiza (y el componente no está memoizado)

El punto 3 es el asesino silencioso. Cada vez que un padre renderiza, todos sus hijos renderizan también. No importa si las props no cambiaron. No importa si el hijo no usa props.

function Parent() {
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>
        Clicks: {count}
      </button>
      <HeavyComponent />  {/* Se renderiza en cada click */}
      <AnotherOne />      {/* También */}
      <Footer />          {/* También */}
    </div>
  );
}

Si HeavyComponent tarda 80ms en renderizar (una tabla con 500 filas, por ejemplo), cada click del botón va a congelar la UI 80ms. El usuario lo siente.

Técnica 1: Levantar el estado hacia abajo

Contraintuitivo, pero poderoso. En lugar de poner el estado en el componente raíz, lo bajás al componente más pequeño que realmente lo necesita.

Antes:

function Dashboard() {
  const [searchTerm, setSearchTerm] = useState("");
  
  return (
    <>
      <SearchBar value={searchTerm} onChange={setSearchTerm} />
      <Sidebar />
      <UserTable />
      <Chart data={heavyData} />
      <Footer />
    </>
  );
}

Cada tecla en el SearchBar renderiza el Dashboard completo. La Chart pesada se recalcula aunque no use searchTerm.

Después:

function Dashboard() {
  return (
    <>
      <SearchBarContainer />  {/* Aisló el estado acá */}
      <Sidebar />
      <UserTable />
      <Chart data={heavyData} />
      <Footer />
    </>
  );
}

function SearchBarContainer() {
  const [searchTerm, setSearchTerm] = useState("");
  
  return (
    <SearchBar value={searchTerm} onChange={setSearchTerm} />
  );
}

Ahora el estado vive en un componente que solo contiene el input. Tipear no toca el resto del árbol. Cero configuración, cero React.memo, solo arquitectura.

Técnica 2: Composición en lugar de memoización

La tentación: envolver todo en React.memo. El resultado: código más complejo, comparaciones de props en cada render, y bugs cuando las comparaciones fallan.

La alternativa: usar children para aislar las partes estáticas.

Antes:

function Layout({ user }) {
  const [theme, setTheme] = useState("dark");
  
  return (
    <div className={theme}>
      <Sidebar user={user} />
      <ExpensiveChart />
      <ThemeSwitcher theme={theme} onChange={setTheme} />
    </div>
  );
}

Cambiar el tema renderiza todo. Tendrías que memoizar Sidebar y ExpensiveChart.

Después:

function Layout({ children, user }) {
  return (
    <div>
      <Sidebar user={user} />
      {children}
    </div>
  );
}

function App() {
  return (
    <Layout user={currentUser}>
      <ThemeSwitcherContainer>
        <ExpensiveChart />
      </ThemeSwitcherContainer>
    </Layout>
  );
}

function ThemeSwitcherContainer({ children }) {
  const [theme, setTheme] = useState("dark");
  
  return (
    <div className={theme}>
      <ThemeSwitcher theme={theme} onChange={setTheme} />
      {children}
    </div>
  );
}

ExpensiveChart ya está instanciado en App. Cuando theme cambia en ThemeSwitcherContainer, ese componente renderiza, pero ExpensiveChart ya es un objeto creado más arriba. React ve que la referencia de children no cambió y no lo toca.

Técnica 3: useMemo y useCallback (pero con criterio)

No memoices todo. Memoizá cuando:

  • Calculás algo costoso (filtros, ordenamientos, transformaciones pesadas)
  • Pasás objetos o funciones como props a componentes memoizados
  • Usás el valor como dependencia de un useEffect

Caso real: filtrado costoso

function UserList({ users, filters }) {
  // Malo: se ejecuta en cada render, incluso si users y filters no cambiaron
  const filteredUsers = users
    .filter(u => u.role === filters.role)
    .filter(u => u.active)
    .sort((a, b) => b.lastLogin - a.lastLogin);
  
  return <Table data={filteredUsers} />;
}

Si el padre renderiza por cualquier motivo (un estado de UI, un timer), este filtrado se ejecuta de nuevo aunque users y filters sean los mismos.

Mejor:

function UserList({ users, filters }) {
  const filteredUsers = useMemo(() => {
    return users
      .filter(u => u.role === filters.role)
      .filter(u => u.active)
      .sort((a, b) => b.lastLogin - a.lastLogin);
  }, [users, filters]);
  
  return <Table data={filteredUsers} />;
}

Caso real: funciones que se pasan como props

function Parent() {
  const [count, setCount] = useState(0);
  
  // Se crea una nueva función en cada render
  const handleSave = () => {
    saveToBackend(count);
  };
  
  return <ExpensiveForm onSave={handleSave} />;
}

Incluso si ExpensiveForm está memoizado con React.memo, recibe una nueva función handleSave en cada render. La memoización falla.

Solución:

const handleSave = useCallback(() => {
  saveToBackend(count);
}, [count]);

Ahora handleSave mantiene la misma referencia entre renders siempre que count no cambie.

Cuándo importa la performance

No optimices si no medís. Usar el Profiler de React DevTools te da números reales:

  • Tiempo de render por componente
  • Cantidad de renders
  • Por qué renderizó (qué prop o estado cambió)

Regla práctica: si un componente tarda menos de 10ms en renderizar y no se renderiza más de 10 veces por segundo, dejalo tranquilo. El costo de la memoización (código más complejo, bugs potenciales) no vale la pena.

Optimizá cuando:

  • El componente tarda >50ms en renderizar
  • Se renderiza muchas veces por interacciones rápidas (tipeo, scroll, drag)
  • Notás lag en la UI (el input se siente lento, las animaciones se traban)

El patrón de contexto que arruina todo

function App() {
  const [user, setUser] = useState(null);
  const [theme, setTheme] = useState("dark");
  const [notifications, setNotifications] = useState([]);
  
  const value = { user, setUser, theme, setTheme, notifications, setNotifications };
  
  return (
    <AppContext.Provider value={value}>
      <Dashboard />
    </AppContext.Provider>
  );
}

Cada cambio en theme, user o notifications crea un nuevo objeto value. Todos los componentes que usan useContext(AppContext) renderizan, incluso si solo leen theme.

Solución: Dividir contextos

<UserContext.Provider value={userValue}>
  <ThemeContext.Provider value={themeValue}>
    <NotificationsContext.Provider value={notificationsValue}>
      <Dashboard />
    </NotificationsContext.Provider>
  </ThemeContext.Provider>
</UserContext.Provider>

O mejor: usar una librería de estado (Zustand, Jotai, Redux) que tiene selectors granulares.

Preguntas frecuentes

¿Debería usar React.memo en todos mis componentes?

No. React.memo agrega overhead: en cada render del padre, React ejecuta la comparación de props. Si las props cambian seguido, estás haciendo trabajo extra al pedo. Usalo solo cuando mediste que un componente renderiza seguido sin necesidad y es costoso.

¿useMemo y useCallback hacen mi app más rápida siempre?

No. Agregan memoria (guardan valores entre renders) y tiempo de comparación (chequean las dependencias). Si el cálculo que memoizás es trivial, perdés performance. Memoizá cuando el costo del cálculo supera el costo de la memoización.

¿Cómo sé si tengo un problema de performance?

Abrí React DevTools, pestaña Profiler, grabá una interacción (tipear, hacer click, scrollear). Si ves componentes que tardan >50ms o que renderizan decenas de veces sin razón, tenés margen de mejora. Si todo está bajo 10ms, no toques nada.

¿Te gustó este artículo?

Descubre nuestros cursos y carreras para llevar tus habilidades al siguiente nivel.