Saltar al contenido

useLayoutEffect y el parpadeo

¿Cuándo vale la pena bloquear la pintura del navegador?

Al terminar vas a poder explicar

  • Explicar la diferencia de timing con useEffect en términos de frames
  • Identificar los pocos casos donde useLayoutEffect es la respuesta correcta
  • Anticipar el costo de un setState dentro de un layout effect

useLayoutEffect tiene exactamente la misma firma que useEffect. La única diferencia es cuándo corre. Y ese cuándo cambia todo.

  • useLayoutEffect corre dentro del commit, después de aplicar los cambios al DOM y antes de que el navegador pinte. Es sincrónico y bloquea la pintura.
  • useEffect corre después del pintado, en una tarea aparte.

Los dos efectos en la misma corrida, con el pintado en el medio

AGENDADOpaso 1 / 89
Traza
Cadena de hooks · <>

Sin hooks en este paso.

Árbol de fibers

  • <HostRoot>

Salida

Nada en el host todavía. React no commiteó ningún cambio.

createRoot().render()

Montaje inicial. No hay árbol previo, así que todo se va a crear desde cero.

VER EL CÓDIGO QUE SE ESTÁ TRAZANDO
1function DosEfectos() {2  const [n, setN] = useState(0);3 4  useLayoutEffect(() => {5    medirYAjustar();          // corre ANTES de pintar6    return () => limpiar();7  }, [n]);8 9  useEffect(() => {10    registrarAnalitica(n);    // corre DESPUÉS de pintar11    return () => cancelar();12  }, [n]);13 14  return <button onClick={() => setN(v => v + 1)}>n = {n}</button>;15}

Con el foco acá: ← → un paso · espacio reproduce · Inicio/Fin van a los extremos.

Ahí está la prueba, sin metáforas. Buscá el paso 🖌 el navegador pinta: el layout effect está arriba, el efecto pasivo está abajo. Poné la traza en «sólo hitos» si querés verlo de un vistazo.

Cuándo importa de verdad

Importa cuando tu efecto lee o modifica el layout y el resultado se ve. El caso canónico es un tooltip:

1useEffect(() => {2  const alto = ref.current.offsetHeight;3  setPosicionY(anclaY - alto);        // ✗ el usuario ve el tooltip mal ubicado4}, [anclaY]);                          //   y después saltar a su lugar5 6useLayoutEffect(() => {7  const alto = ref.current.offsetHeight;8  setPosicionY(anclaY - alto);        // ✓ se corrige antes de que se pinte nada9}, [anclaY]);

Con useEffect, el navegador pinta el tooltip en la posición vieja, después corre el efecto, después re-renderiza y vuelve a pintar. Son dos frames y el usuario ve el salto. Con useLayoutEffect, todo eso pasa antes del primer pintado. Un frame. Sin parpadeo.

El criterio

Usá useEffect por defecto. Pasate a useLayoutEffect sólo cuando se cumplan las dos condiciones a la vez:

  1. El efecto mide o ajusta algo del layout, y
  2. ves un parpadeo real, no uno hipotético.

Suscripciones, pedidos a la red, analítica, temporizadores: nada de eso mira el layout. Ponerlos en un layout effect no arregla nada y retrasa la pintura.

Antes de seguir · predecí

Tu efecto se suscribe a un WebSocket y actualiza una lista de mensajes. ¿useEffect o useLayoutEffect?

Lo que te llevás

Misma firma, distinto momento. useLayoutEffect corre dentro del commit y bloquea la pintura; useEffect corre después y no la bloquea. Elegí el primero sólo cuando medís layout y el parpadeo es real.

Antes de marcarla, comprobá

  • ¿Podés explicar explicar la diferencia de timing con useEffect en términos de frames?
  • ¿Podés explicar identificar los pocos casos donde useLayoutEffect es la respuesta correcta?
  • ¿Podés explicar anticipar el costo de un setState dentro de un layout effect?