Ambos executam efeitos, mas em momentos diferentes em relação à pintura do navegador — e essa diferença de timing é o ponto principal.
useEffectexecuta depois que o navegador já pintou. É assíncrono e não-bloqueante. Use para quase tudo (dados, subscrições, logging).useLayoutEffectexecuta sincronamente depois que o DOM é mutado, mas ANTES do navegador pintar. Use quando você precisa ler o layout e mudar o DOM no mesmo frame para evitar uma oscilação visível.
