26 oct 2025·6 min de lectura

Solucionar bucles infinitos de re-render en React: trampas de patrones de IA

Aprende a solucionar bucles infinitos de re-render en React identificando trampas de estado y efectos generadas por IA, y siguiendo un flujo de trabajo paso a paso para estabilizar las actualizaciones.

Solucionar bucles infinitos de re-render en React: trampas de patrones de IA

Cómo se ve un bucle infinito de re-render

Un bucle infinito de re-render ocurre cuando un componente React se sigue renderizando una y otra vez sin llegar a estabilizarse. En lugar de un ciclo de render normal, algo sigue provocando una actualización.

Las primeras señales suelen ser visibles para el usuario: la página se siente atascada, los botones dejan de responder, los inputs van lentos o la interfaz parpadea porque el estado cambia más rápido de lo que el navegador puede pintar.

Síntomas técnicos comunes:

  • Una superposición de error en rojo como "Too many re-renders. React limits the number of renders to prevent an infinite loop"
  • Picos de CPU y la pestaña se vuelve caliente o lenta
  • La misma petición de red enviándose repetidamente
  • Logs en la consola imprimiéndose sin parar
  • UI que se restablece sola (por ejemplo, un modal que se vuelve a abrir justo después de cerrarlo)

Estos bucles son más que un problema de rendimiento. Rompen la lógica. Un formulario nunca termina de validar, las comprobaciones de autenticación rebotan entre "logged in" y "logged out", y efectos que deberían ejecutarse una vez se ejecutan cientos de veces. Eso suele crear errores adicionales como eventos duplicados de analítica, toasts repetidos o APIs con límites de tasa alcanzados.

Un escenario común: un dashboard carga, vuelve a pedir datos, guarda el estado con la respuesta, y luego vuelve a pedir porque un efecto depende de algo que cambia en cada render. El usuario ve un spinner que nunca termina y tu backend recibe una avalancha de peticiones idénticas.

Por qué el código React generado por IA suele entrar en bucles

Las herramientas de IA pueden producir React que parece correcto en una demo, pero se rompe con datos reales y usuarios reales. Una razón importante es mezclar tres cosas que deberían permanecer separadas:

  • Estado fuente (lo que almacenas)
  • Valores derivados (lo que calculas a partir de lo almacenado)
  • Efectos secundarios (lo que haces porque algo cambió)

Cuando eso se mezcla, terminas actualizando estado durante el render, o ejecutando un efecto que cambia los mismos valores que hacen que el efecto vuelva a ejecutarse.

Patrón 1: "Hacer todo en un solo useEffect"

Los componentes generados por IA suelen meter fetching, parsing, filtrado y estado de UI en un solo efecto. El efecto se ejecuta, llama a setState varias veces, vuelve a renderizar y luego se ejecuta de nuevo porque cambió alguna dependencia.

El bucle suele verse así: fetch -> setState -> re-render -> efecto se ejecuta otra vez -> fetch otra vez.

Patrón 2: Dependencias inestables creadas en cada render

Otra causa común es poner valores en el array de dependencias que se recrean en cada render, aunque parezcan “los mismos”. Ejemplos: objetos/arrays inline, funciones inline o valores derivados reconstruidos en cada render.

React compara dependencias por referencia, no por contenido profundo. Una nueva referencia de objeto significa “cambió”, así que el efecto se ejecuta otra vez.

Un escenario realista: un dashboard construye queryParams como un objeto dentro del componente y lo usa en useEffect para hacer fetch. Cada render crea un nuevo objeto, así que el efecto se dispara de nuevo, vuelve a pedir, establece estado otra vez y el dashboard nunca se estabiliza.

Cómo confirmar dónde empieza el bucle

Primero, confirma que es un bucle real y no solo “muchos renders”. Añade un contador al inicio de la función del componente.

console.count('Component render')

Si el contador sube sin control aun cuando no interactúas con la página, lo has confirmado.

A continuación, aísla el disparador. Desactiva temporalmente los efectos hasta que el bucle pare. La forma más rápida es comentar bloques useEffect uno por uno (o hacer que devuelvan pronto) y recargar tras cada cambio. Cuando el bucle pare, el último cambio apunta a la fuente.

Un orden práctico:

  • Bloques useEffect que establecen estado o escriben en almacenamiento
  • Efectos que llaman APIs o se suscriben a algo
  • Estado derivado computado o sincronizado en cada render
  • Providers de contexto y componentes padres

React DevTools también puede ayudar. Activa resaltar actualizaciones y observa qué parte de la UI parpadea repetidamente. Eso suele indicar si el bucle está en el componente actual o un nivel arriba.

Si la pestaña Network muestra la misma petición una y otra vez, la cadena suele ser: render -> efecto -> fetch -> setState -> render. Arreglar el bucle también evita peticiones duplicadas y problemas con límites de tasa.

Flujo de trabajo paso a paso para detener el bucle

Trata un bucle de re-render como una reacción en cadena. Una actualización causa el siguiente render, que causa la siguiente actualización. Tu trabajo es encontrar el primer dominó.

  1. Identifica la actualización que ocurre justo antes del siguiente render.

Observa qué setter se ejecuta (setUser, setItems, setLoading) y qué lo dispara. Si hay varios setters, coméntalos uno a la vez para ver cuál detiene el ciclo.

  1. Asegúrate de no actualizar estado durante el render.

Un error común es llamar a un setter mientras "calculás" valores, o dentro de un helper que se ejecuta mientras se construye el JSX. Las actualizaciones de estado pertenecen a handlers de eventos, efectos o callbacks, no al cuerpo del render.

  1. Reduce el efecto a su trabajo real.

Reduce el efecto a la versión más pequeña que aún reproduzca el bug. Anota de qué depende realmente (props, state y cualquier valor externo que lea). Los problemas de dependencias suelen esconderse aquí.

  1. Estabiliza las entradas y haz que las actualizaciones sean idempotentes.

Algunas soluciones que detienen bucles rápidamente:

  • Haz que las entradas inestables sean estables (memoiza callbacks/objetos o muévelos fuera del componente)
  • No almacenes valores derivados en estado salvo que sea necesario (calcula desde props/state o usa useMemo)
  • Protege las actualizaciones para llamar a setState solo cuando el valor realmente cambió
  • Añade limpieza para suscripciones, timers y peticiones en vuelo

Si un efecto "sincroniza" un valor en otro, debe ser seguro ejecutarlo más de una vez. Ejecutar un efecto dos veces no debería cambiar el estado a menos que sus entradas realmente hayan cambiado.

Arreglando problemas de dependencias en useEffect

Limpia una arquitectura React enmarañada
Desenredamos componentes espagueti para que estado, valores derivados y efectos permanezcan separados.

La mayoría de los bucles de useEffect se reducen al mismo patrón: el efecto establece estado, y ese cambio de estado hace que el efecto se ejecute otra vez.

Trata cada setState dentro de un efecto como sospechoso hasta que puedas explicar por qué se detiene.

Regla clave: no establezcas estado incondicionalmente dentro de un efecto. Si el efecto se ejecuta al montar y al cambiar dependencias, necesitas una condición que evite una actualización repetida.

Una protección práctica es "solo actualizar cuando el siguiente valor sea realmente distinto". Esto importa cuando el código reconstruye arrays u objetos y los guarda en estado cada vez, aun cuando el contenido no cambió.

Buenas soluciones:

  • Comparar antes de llamar a setState (o comparar dentro de la actualización funcional)
  • Memoizar dependencias cuando realmente necesites depender de objetos/funciones
  • Calcular valores derivados durante el render en lugar de sincronizarlos vía efecto
  • Mantener las dependencias intencionales (depender de primitivos cuando sea posible)

Además, no trates el array de dependencias como una lista de verificación. Los linters ayudan, pero silenciar una advertencia añadiendo una dependencia puede convertir un efecto de configuración única en un bucle auto-disparador. A menudo la solución real es reestructurar: dividir un efecto en dos efectos más pequeños o mover trabajo a un handler de evento.

Actualizaciones de estado que provocan re-renders por accidente

Es fácil obsesionarse con useEffect, pero algunos bucles vienen de errores simples de estado.

Establecer estado durante el render

Si llamas a setState en el cuerpo del render, React no tiene otra opción que renderizar otra vez. Esto puede ser directo (un setX(...) simple) o indirecto (un helper que llamas desde el render que actualiza estado). Incluso algo que parece inofensivo, como "normalizar datos si faltan", puede convertirse en un bucle.

Estado espejo (estado derivado persiguiendo props)

Otra trampa es copiar props en estado y luego "sincronizar" cuando no coinciden. Si la prop es un objeto nuevo en cada render, tu lógica de sincronización nunca para.

Algunos patrones que suelen causar renders repetidos:

  • Actualizar estado mientras se renderiza (incluyendo helpers llamados desde JSX)
  • Almacenar valores derivados en estado en lugar de calcularlos
  • Crear arrays/objetos nuevos y establecerlos en cada render sin comprobar igualdad
  • Pasar una prop key cambiante que fuerza remounts

Cuando el siguiente estado depende del anterior, usa actualizaciones funcionales. En lugar de setCount(count + 1), usa setCount(c => c + 1). Evita valores obsoletos que pueden disparar actualizaciones de "corrección".

Si tienes varias actualizaciones relacionadas que se retroalimentan (loading, errores, reintentos, datos cacheados), useReducer puede ayudar manteniendo las transiciones en un solo lugar.

Trampas en fetch, suscripciones y limpieza

Los bucles a menudo se esconden en efectos "normales": fetches, suscripciones y timers. Si cada callback llama a setState, puedes acabar con re-renders constantes incluso si la UI parece no cambiar.

Fetches que se siguen ejecutando

Si un fetch está ligado a un estado que el propio fetch actualiza, obtienes un bucle.

Haz las peticiones cancelables para que las respuestas obsoletas no sobreescriban estado más nuevo. Usa AbortController dentro del efecto y aborta en la limpieza.

Para reducir duplicados, añade una simple protección usando un ref (no state), por ejemplo rastreando una clave de petición en curso.

Suscripciones, timers y limpieza

Los listeners y timers pueden dispararse para siempre si olvidas la limpieza. Una regla sencilla: todo "start" necesita un "stop" correspondiente en la función de cleanup.

Limpia intervals/timeouts, desuscríbete de listeners y elimina manejadores de eventos.

Una fuente de datos, un escritor

Evita actualizar la misma porción de estado desde múltiples lugares. Si un fetch establece profile, una suscripción también establece profile, y otro efecto "sincroniza" profile, has creado un bucle de retroalimentación. Elige un propietario único para las escrituras. Los demás pueden disparar un refresh, no escribir el mismo estado.

Errores comunes que mantienen vivo el bucle

Detén el ciclo interminable de re-renderizados
Reparamos código React generado por IA que entra en bucles, vuelve a solicitar datos y restablece el estado inesperadamente.

Algunos bucles "desaparecen" cuando comentas una línea y vuelven cuando la añades otra vez. Eso suele significar que el disparador subyacente sigue ahí.

StrictMode hace visibles los efectos inseguros

Si un efecto se ejecuta dos veces en desarrollo, React StrictMode puede estar haciendo su trabajo. No apagues StrictMode para ocultarlo. Haz el efecto seguro para ejecutarse más de una vez añadiendo limpieza, protecciones o moviendo la inicialización de una sola vez fuera del efecto.

Clausuras obsoletas crean actualizaciones de "compensación"

Un patrón común es un efecto que lee estado viejo y luego lo "corrige" con setState. Esa corrección desencadena un nuevo render, que crea otra lectura obsoleta, y el ciclo se repite.

Si un efecto usa estado pero la lista de dependencias no coincide, puedes acabar luchando con tus propios valores pasados. Prefiere actualizaciones funcionales cuando necesites el valor más reciente.

La memoización también puede volverse en tu contra. useCallback/useMemo con dependencias incorrectas sigue creando una nueva función/objeto cada render. Si ese valor está en un array de dependencias, tu efecto se ejecutará cada vez.

Formas rápidas de detectar un bucle que sigue vivo:

  • Loguea dependencias "estables" (funciones, objetos, arrays) y mira si cambian en cada render
  • Calcula objetos derivados dentro del efecto a partir de dependencias primitivas
  • Asegúrate de que cada suscripción/listener tenga limpieza
  • Evita sincronizar estado con props salvo que haya una razón clara

Lista rápida antes de lanzar

Haz una pasada final con el comportamiento de usuarios reales en mente. Los bucles suelen desaparecer en el camino feliz y volver cuando los usuarios clican rápido, cambian de pestaña o tienen redes lentas.

  • Busca setters que puedan ejecutarse durante el render (incluyendo helpers llamados desde JSX)
  • Lee cada useEffect como una frase: “Cuando X cambia, hacer Y.” Asegúrate de que haya una condición de parada
  • Revisa la estabilidad de dependencias (objetos, arrays y funciones inline cambian en cada render)
  • Verifica limpieza para timers, listeners, suscripciones y observers
  • Haz que el trabajo de red sea resistente: cancela peticiones obsoletas, deduplica llamadas e ignora respuestas tardías

Una prueba simple: abre la página y cambia un filtro tres veces rápido. Si ves peticiones superpuestas y la UI se sigue "corrigiendo", probablemente tienes dependencias inestables y falta de cancelación.

Un ejemplo realista: el dashboard que se sigue refetching

Haz que las peticiones de datos se estabilicen
Estabilizaremos las entradas de useEffect, añadiremos salvaguardas y preveniremos solicitudes repetidas.

Un caso común en dashboards generados: cargar una lista de usuarios, guardarla en estado y mostrar una tabla. Todo parece bien, salvo que la página vuelve a pedir constantemente y la UI tiembla.

El bug

El patrón suele empezar con un efecto que depende de un objeto inline. Ese objeto se recrea en cada render, por lo que React lo trata como "cambió" cada vez.

// Problem
function AdminUsers({ orgId }) {
  const [users, setUsers] = React.useState([]);

  const options = { method: "GET", headers: { "x-org": orgId } }; // new each render

  React.useEffect(() => {
    fetch("/api/users", options)
      .then(r => r.json())
      .then(data => setUsers(data));
  }, [options]);

  return <UsersTable users={users} />;
}

Algún código lo empeora "normalizando" la respuesta en un array completamente nuevo cada vez, así que llama a setUsers aunque nada haya cambiado.

La solución

Estabiliza las entradas del efecto, evita actualizaciones sin sentido y cancela peticiones en vuelo.

function AdminUsers({ orgId }) {
  const [users, setUsers] = React.useState([]);

  const options = React.useMemo(
    () => ({ method: "GET", headers: { "x-org": orgId } }),
    [orgId]
  );

  React.useEffect(() => {
    const controller = new AbortController();

    fetch("/api/users", { ...options, signal: controller.signal })
      .then(r => r.json())
      .then(data => {
        setUsers(prev => (sameUsers(prev, data) ? prev : data));
      })
      .catch(err => {
        if (err.name !== "AbortError") throw err;
      });

    return () => controller.abort();
  }, [options]);

  return <UsersTable users={users} />;
}

Para verificar que funcionó:

  • Añade un contador de renders y confirma que deja de subir
  • Observa la pestaña Network: las peticiones repetidas deberían parar
  • Cambia orgId una vez y confirma que obtienes exactamente un nuevo fetch

Un pequeño refactor ayuda a que el bug no vuelva: extrae el fetch a un hook useUsers(orgId), nombra las valores memoizados con claridad y mantiene las dependencias de los efectos cortas y estables.

Siguientes pasos si el código sigue en bucle

Si arreglaste el problema obvio (como un array de dependencias faltante) y la app aún gira, asume que hay más de un disparador. Muchos bucles son una cadena: una actualización de estado dispara un efecto, ese efecto actualiza otra cosa y otro componente reacciona escribiendo de vuelta en el primer estado.

Una pequeña corrección basta cuando puedes señalar una causa clara, como un efecto que establece estado en cada ejecución o un callback prop que cambia de identidad en cada render.

Una reescritura suele ser mejor cuando un componente hace demasiado: fetching, ordenado, filtrado, estado de formulario y estado de UI mezclados. Si sigues añadiendo flags de “solo ejecutar una vez”, estás tratando síntomas.

Si heredaste una base de código React generada por IA que no se estabiliza, FixMyMess (fixmymess.ai) puede ayudar trazando la cadena de disparadores y reparando el flujo subyacente de estado y efectos, no solo añadiendo protecciones. Una auditoría de código gratuita suele ser suficiente para localizar el bucle exacto y la corrección segura más rápida.