Tres bugs de interfaz que no muestran ningún error
Los bugs que lanzan un error son fáciles de encontrar. Los difíciles son los que no avisan: la interfaz se ve casi bien, o funciona casi siempre. Estos tres aparecieron en sistemas en producción y comparten esa característica. Cada uno dejó una regla que ahora aplico por defecto.
1. Un flex-1 que ignora su altura
Una app tipo gestor de archivos tenía el diseño clásico: barra lateral fija y un área de contenido que debe desplazarse por dentro. Con muchos archivos, en lugar de desplazarse solo el área de contenido, se desplazaba la ventana entera y la barra lateral se perdía hacia arriba.
El contenedor de la app tenía flex-1, una altura igual a la
de la ventana y el desbordamiento oculto. Antes de cargar contenido, su
altura calculada era correcta. Después de inyectar trescientas filas de
prueba, medía más de doce mil píxeles.
La causa: en Tailwind, flex-1 define una base flexible de
cero, y en un contenedor en columna esa base tiene prioridad sobre la
altura declarada del elemento. El padre, el body, solo tenía
altura mínima y no una altura definida, así que no había espacio real que
repartir y el navegador volvía a dimensionar por contenido, ignorando la
altura explícita.
La corrección tuvo dos partes. El contenedor raíz de la
app pasó a posición fija ocupando toda la ventana, lo que lo ata al
viewport sin depender de su padre. Y cada elemento que debe desplazarse
por dentro recibió min-height: 0 en sí mismo: por defecto, un
elemento flexible no se achica por debajo de su contenido, y ponerlo solo
en un ancestro no alcanza.
2. Botones que no se sienten clicables
En una página de planes, los botones cambiaban de brillo al pasar el mouse, pero algo se sentía mal. El cursor seguía siendo la flecha normal, no la mano. La propiedad calculada lo confirmó: el cursor de los botones era el predeterminado.
La versión de Tailwind en uso no restablece el cursor de mano en los
elementos button. Como el efecto al pasar el mouse sí
funcionaba, a simple vista parecía un botón normal. Nadie lo notaba
hasta que alguien decía que la página "no se sentía clicable" sin saber
explicar por qué. Curiosamente, el componente de casilla del mismo
archivo sí tenía el cursor explícito: alguien ya se había topado con el
problema, pero no lo aplicó a los botones.
La corrección: cursor de mano explícito en todos los componentes de botón compartidos, y de paso un estado activo al presionar, desactivado cuando el botón está deshabilitado.
3. Un useEffect que revienta al desmontar
Una app en React fallaba al salir de la pantalla de conversación con un error minificado del tipo "n is not a function", sin una línea útil.
El efecto que desplazaba la conversación al final estaba escrito con una
función flecha de cuerpo conciso, que devuelve el valor de su expresión.
React guarda lo que devuelve un efecto como su función de limpieza. El
método de desplazamiento del elemento, contra lo que uno supondría, no
devuelve undefined en Chrome: devuelve un objeto. Al
desmontar, React intentaba llamar a ese objeto como función.
Se diagnosticó sin mapas de código fuente: descargando el paquete de producción y buscando las funciones de la traza, que resultaron ser las de React que ejecutan las limpiezas de efectos. Eso confirmó que el culpable era un efecto que devolvía algo raro, y una búsqueda de efectos que reciben una función por nombre en vez de una en línea encontró al sospechoso.
La corrección: agregar llaves al cuerpo de la función,
lo que hace que devuelva undefined. La regla: en un
useEffect, o se devuelve una función de limpieza, o no se
devuelve nada.
Lo que tienen en común
- Los tres se diagnosticaron midiendo en un navegador real, no leyendo el código.
- Los tres tenían una parte que sí funcionaba y ocultaba la que no.
- Los tres se evitan con una regla aplicada por defecto en componentes compartidos.