Ayer, miércoles 16 de agosto, desde el equipo de QA me llegó un reporte de un error en uno de los desarrollos que había realizado, un error atípico y difícil de diagnosticar. Estuve en la mañana colocando Logs y debuggers por todos lados, los componentes afectados en los últimos cambios aparentemente no mostraban inconsistencias y renderizaciones fuera de lo normal. Pude replicar el error, pues sucedía al desplazarme por una serie de Tabs y el contenido correspondiente a cada Tab provocaba bloqueos en el sitio web. Usar la extensión React Devtools era imposible, pues al tener un error de bloqueo total de la pagina no era posible navegar, inspeccionar y usar extensiones de Chrome durante el crash.
Al tener un bloqueo de la pagina web es común pensar en una serie de excepciones que van rompiendo todo, también es cierto que en un contexto de desarrollo web puede tratarse de iteraciones indefinidas de un mismo elemento, componente o función, por eso, al llegar a la conclusión de que el problema debía ser un renderizado incontrolado de algún componente de React esta debía ser la sospecha #1.
Primer intento
Revisión superficial del problema y diagnostico inicial. Los nuevos cambios involucraban llamados a diferentes
Queries de Grahpql y renderizado de componentes nuevos, por ello fue necesario validar los siguientes puntos:
- Peticiones: Desde el navegador web, abrir el inspeccionador de elementos e ir hasta el Tab "Red" (o "Network" en Inglés) para validar que no existan llamados recurrentes e innecesarios de los queries de Graphql.
- Resultado: Todo a la normalidad.
- Errores en consola: Revisar que no se tengan errores o advertencias en la consola del navegador (Tab "Consola").
- Resultado: Todo a la normalidad.
- Profiling: Intentar hacer Profiling de los componentes de React usando la extension React Devtools.
- Resultado: No se pudo realizar profiling debido al bloqueo de la pagina al momento de activar la opción cuando el bug se había presentado.
- Error Boundary: Agregar como padre uno de los componentes custom llamado ErrorBoundary que sirve par capturar errores en los componentes hijos.
- Resultado: No hubo errores, por lo tanto ErrorBoundary estaba puesto en vano.
Segundo intento
Lo primero que debía hacer era descartar que se trataba de un error en los componentes principales, por eso, el segundo intento para detectar el problema fue abrir el inspeccionador de elementos de Google Chrome y desde la opción de "Fuentes" (o "Sources" en Inglés) activar la casilla "Detenerse cuando se detecten excepciones". Una vez activada la detección de excepciones se procede a navegar en el sitio web para desencadenar todo tipo de excepciones sea cual sea que ocurran durante la navegación, así cuando el Bug se presente lo podremos ver en cuestión (siempre y cuando arroje una excepción).
Un par de horas después, había realizado el proceso de depuración de varios errores, encontrando y solucionando algunos problemas mínimos con la ayuda del step-by-step del inspecionador del navegador web. Este primer intento se consolida al detectar varios errores provocados por la carga Lazy Load de algunos componentes de React, sin embargo, tenia que intentar esta opción a pesar que no sirva para detectar errores de renderizado infinito en React. En conclusión, este segundo intento me sirvió para encontrar puntos de mejora y validaciones extra, pero no para hallar el problema real.
Tercer intento
Después de despejar la mente, almorzar y caminar un rato por la calle, volví a casa, hice masa de pan y la puse en un recipiente para que la levadura se activara y la masa creciera, luego abrí el sitio web de
Mercado Libre (o MELI) para buscar algunos productos de intereses, y al cabo de unos minutos no había comprado nada pero si me encontraba inspeccionando el sitio web con la extension de React Devtools, encontré que algunos componentes de MELI se están renderizando de forma innecesaria con solo hacer unas pocas acciones como un hover, también vi que usan servicios Graphql y las traducciones de los productos vienen desde Backend.
En fin, durante esta inspección he terminado descubriendo de forma accidental y activando la casilla "Highlight updates when components render" ubicada en "React Developer Tools > View Settings > General > Highlight updates when components render". Activar la visualización de cambios me permitió ver cada update en los componentes de MELI, esta característica resultó ser muy útil para identificar qué componentes se están actualizando durante la navegación en el sitio web.
Una vez encontrada y activada esta característica, seguí indagando sobre el problema inicial. Durante el primer diagnostico, al intentar hacer Profiling durante la replicación del problema no se lograba iniciar y procesar debido al bloqueo de la pagina, provocando un colapso de todo el sitio web e impidiendo realizar cualquier acción, la única salida era el cierre forzado de la pestaña del navegador, sin embargo, una vez dejando activada la visualización de cambios podía navegar por el sitio web y luego replicar el problema para poder observar segundos antes del colapso qué componentes se estaban renderizando de manera descontrolada. Y así fue que logré identificar el componente a corregir.
Todo se resumió a un problema, regeneración de referencias de objetos que pasan como props a otros componentes. Los componentes recibían estos props y realizaban una acción con un
useEffect, este luego realizaba otra acción que cambiaba la referencia de una de sus dependencias, y así sucesivamente hasta el final de los tiempos. Esto provocaba una cantidad de iteraciones indefinidas en el ciclo de ejecución de un componente, haciendo que a su vez los componentes hijos también sufrieran un re-renderizado. En fin, todo esto ocurrió al trabajar en la versión "16" de React, en las versiones posteriores también deben haber problemas de
infinite loops en useEffects pero se deberían controlan mejor para evitar crashes en el sitio web.
Conclusiones
- La Persistencia en la Depuración: A pesar de la complejidad y frustración inicial, aprendí la importancia de persistir en la depuración y no rendirte ante problemas difíciles de diagnosticar.
- Enfoque Iterativo: Descubrí que abordar un problema técnico de manera iterativa, probando diferentes enfoques y herramientas, puede llevar finalmente a la identificación y resolución del problema.
- Exploración de Herramientas de Desarrollo: Mi experiencia mostró la utilidad de aprovechar al máximo las herramientas de desarrollo disponibles, como el inspector de elementos y las extensiones como React Devtools, para analizar y entender el comportamiento de tus componentes.
- Descubrimiento Casual: A veces, los descubrimientos más valiosos pueden surgir de manera casual. El hallazgo accidental de la opción "Highlight updates when components render" ilustra cómo explorar diversas características puede revelar soluciones inesperadas.
- Control de Referencias y Ciclos de Vida: Aprendí sobre la importancia de controlar las referencias y las actualizaciones en los componentes de React, especialmente al trabajar con efectos en el ciclo de vida de los componentes.
Finalmente, la pregunta del millón, por qué pasé por todo esto? Porque de forma local en desarrollo no había forma de probar nada por múltiples motivos técnicos, todos los cambios debí enviarlos al branch de QA directamente para después poder observar si lo que había programado estaba bien, así todo el proceso se complicaba y por ende la detección de errores.
Si te gusta la publicación o tuviste alguna experiencia igual o similar déjamelo saber en los comentarios, gracias por leer.
No hay comentarios.:
Publicar un comentario