Pick a depth. Each prompt opens in your AI pre-loaded with the lesson. Click a row to preview the prompt.
useLayoutEffect fires synchronously after React mutates the DOM but before the browser paints. This makes it the correct tool when you need to read a DOM measurement (getBoundingClientRect, scrollHeight) and immediately apply a visual adjustment — doing the same read in useEffect would show a frame where the adjustment hasn't been applied yet, causing a visible flash or layout jump. The cost is real: synchronous work in useLayoutEffect is on the critical rendering path. Reserve it strictly for DOM measurement-then-adjustment flows; useEffect handles everything else.
useLayoutEffect runs synchronously after DOM mutations, before paint. Use it when you need to read layout and apply a style change without the user seeing an intermediate state.
console.time('layout') at the start and console.timeEnd('layout') at the end. Do the same in a useEffect version. Compare the times — both should be similar, but the layout version blocks the paint thread.while (Date.now() < Date.now() + 50) {} (50ms busy loop) inside useLayoutEffect. Notice the entire frame is delayed — the browser won't paint until useLayoutEffect completes. Do the same in useEffect and observe the component renders first, then the effect fires.import { useRef, useState, useLayoutEffect, useEffect } from 'react';
// useLayoutEffect: tooltip positions itself BEFORE paint — no flash
export function Tooltip({ text }: { text: string }) {
const ref = useRef<HTMLDivElement>(null);
const [style, setStyle] = useState<React.CSSProperties>({});
useLayoutEffect(() => {
const el = ref.current;
if (!el) return;
const { right } = el.getBoundingClientRect();
if (right > window.innerWidth) {
// nudge left before the browser has a chance to paint
setStyle({ transform: 'translateX(-100%)' });
}
}, [text]);
return (
<div ref={ref} style={{ position: 'absolute', ...style }}>
{text}
</div>
);
}
// useEffect: same code but causes a visible flicker
export function FlickyTooltip({ text }: { text: string }) {
const ref = useRef<HTMLDivElement>(null);
const [style, setStyle] = useState<React.CSSProperties>({});
useEffect(() => {
const el = ref.current;
if (!el) return;
const { right } = el.getBoundingClientRect();
if (right > window.innerWidth) {
setStyle({ transform: 'translateX(-100%)' });
// setState triggers a second render AFTER the first painted frame — flash!
}
}, [text]);
return <div ref={ref} style={{ position: 'absolute', ...style }}>{text}</div>;
}