React’s useLayoutEffect vs useEffect hooks handle side effects but differ in timing. useEffect runs after the browser paints the screen (post-paint), while useLayoutEffect runs synchronously after DOM changes but before painting (pre-paint). This timing difference determines which you should use.
My take is to prefer useEffect by default. Only reach for useLayoutEffect when you need to prevent VISIBLE UI glitches or require accurate pre-paint measurements (like tooltip positioning). Using the wrong hook creates jank or performance issues.
This guide shows 7 real UI fixes where useLayoutEffect prevents flicker, plus classic useEffect use cases. I’ll provide short code snippets and pitfalls (SSR warnings, StrictMode double execution). Each fix targets measurable improvements, such as reduced CLS (Cumulative Layout Shift) and smoother first paint.
The Core Difference (Timing, Paint, and the Commit Phase)
React’s timeline is render → commit → paint.
Render creates the virtual DOM, during which React calculates what changed. It calculates the layout’s positions and sizes.

In commit, it updates the DOM. Then the browser paints those changes to the screen.
useEffect runs AFTER commit and paint (asynchronously). Perfect for non-visual side effects like data fetching, analytics, or timers. These DON’T affect what users see, so they can happen after painting without causing flicker.
useLayoutEffect runs synchronously after DOM mutations but BEFORE paint. This blocks painting until your effect completes. Ideal for measuring elements — getBoundingClientRect() — or adjusting layout to prevent visible glitches.
A more complete flow looks like this: Render → DOM Mutations → useLayoutEffect → Paint → useEffect

Here’s a measurement example:
useLayoutEffect(() => { const { height } = elementRef.current.getBoundingClientRect(); setTooltipHeight(height); // Triggers re-render before paint }, []);
Don’t expect magic speed-ups from useLayoutEffect
. It’s about correctness, not performance!
Hook | Timing | Performance | Best For |
---|---|---|---|
useLayoutEffect | Synchronous after DOM mutations, before paint | Blocks paint | Reading layout, writing styles/position, preventing flicker |
useEffect | Asynchronous after paint | Non-blocking | Data fetching, subscriptions, analytics, timers, storage, URL sync |
Overusing useLayoutEffect blocks paint and slows pages. Use it when visual correctness requires pre-paint execution. Also, useLayoutEffect is client-only. On the server, it does nothing and warns about hydration mismatches. Use conditional rendering or useIsomorphicLayoutEffect to fix this.

By the way — Did you know that react 19 is out? Read more in my post “New React 19 Actions — Guide to useOptimistic, useActionState, and useFormStatus Hooks“
Rule of Thumb That Actually Works in Real Apps
Simple rule: “If users could see a wrong frame, use useLayoutEffect
. Otherwise, useEffect
.”
Use useLayoutEffect
for:
- Reading layout (element dimensions, positions)
- Writing styles or positions
- Positioning elements (tooltips, dropdowns)
- Preventing flashes or jumps
Use useEffect
for:
- Data fetching
- Analytics tracking
- Timers and intervals
- URL synchronization
- Storage operations
Boxed tip: Start with
useEffect
. Switch only if you spot UI lags/flickers
The difference comes down to visual impact. Layout effects fix what users see. Regular (use) effects handle behind-the-scenes work that doesn’t affect rendering immediately.
1. Measure an Element’s Size on Mount — Avoid 0px Flash
One of the classic useLayoutEffect vs useEffect scenarios happens when you try to measure an element’s size right after it’s first rendered. If you use useEffect, React will paint once with width or height set at 0px. Then it updates the real dimension on the next frame. That means—boom, ugly jump, flick, or text reflows.
That quick snap from 0px → real width ruins a smooth UI/UX (I hate that—it just feels cheap). It also tanks CLS (Cumulative Layout Shift), making your app fail the “first frame stability” check. Users see the “pop” and think the UI’s glitchy…
…To fix, use useLayoutEffect. It runs after DOM mutations but BEFORE the browser paints. That means React measures the element (via getBoundingClientRect) and stores the size in state instantly. By the time the first paint happens, the right width is already locked in. Result → “steady width” from the start!
Here’s the basic code (short version):
function Box() { const ref = React.useRef(null); const [w, setW] = React.useState(0); React.useLayoutEffect(() => { setW(ref.current.getBoundingClientRect().width); }, []); return <div ref={ref}>Width: {w}px</div>; }
Without useLayoutEffect, you’d see “Width: 0px” then jump to “Width: 120px”. With it, you’ll get “Width: 120px” from the very first paint.
Keep the work minimal — just measure width/height. Do NOT shove style calculations or heavy math inside useLayoutEffect. If you can, precompute styles in CSS or set fallback sizes.\
2. Autofocus Instantly On Open (No Visible Delay)
When you render a modal (or a new form section) onto the screen, users expect the typing caret to blink right away. If focus lands late (even by a single frame), it feels buggy. That tiny flicker suggests the app has lost its polish. This is where the whole useLayoutEffect vs useEffect debate gets real.
If you run ref.current.focus() in useEffect, the DOM paints once without focus. Then, the jump to the field when it is visible. That micro-delay breaks trust in UI quality.
With useLayoutEffect, React runs focus logic before paint, so the caret is there instantly!! Here’s the code sample:
function AutoFocusInput() { const ref = React.useRef(null); React.useLayoutEffect(() => { ref.current?.focus(); }, []); return <input ref={ref} placeholder="Type here..." />; }
Accessibility still matters. Sometimes, it’s safer to focus on the container first, then move inside. WCAG talks about respecting user motion and preference — allow an escape hatch (like disabling autofocus).
3. Scroll To a Target With No Snap
Ever notice that jump when a page loads? Yeah, that is a “paint-then-snap” moment that feels glitchy. Now you know it happens when React renders a page and then scrolls to an anchor. When you see it — it looks messy.
With useLayoutEffect, wrap useLayoutEffect() hook inside the scrollIntoView() function, so that the scroll happens before the first paint. That means when the browser shows the first frame, it’s already at the correct position—no distracting jump. Using useEffect() will initially display the top of the page, then snap down (which can look clunky, especially on a slow CPU or network).
function ScrollOnMount() { const ref = React.useRef(null); React.useLayoutEffect(() => { ref.current?.scrollIntoView({ block: "start" }); }, []); return <div ref={ref}>Target</div>; }
Accessibility also plays a role here. Skip links should land instantly at the target — screen readers and keyboard nav users thank us for this.
Test your app with a throttled CPU and a slow network to really spot that first-frame behavior. If you only see the target right away (no jump), you nailed it.
4. Apply Class Based on Measurement to Avoid Clamping Overflowing Text
Ever seen a header snap — two lines at first, then shrink into one? It happens when long text (or hero copy) loads wider than its container, then clamps down after styles are applied. Using useLayoutEffect vs useEffect is the deciding factor here, too.
With the useEffect hook, React waits until after paint, meaning it will display your brand header with ugly overflow for 1 frame. With useLayoutEffect, you measure widths BEFORE paint, then apply the clamp class instantly — no flash, no jitter. That’s why, for typography fixes (especially resizable brand headers, hero banners, or marketing copy), useLayoutEffect feels mandatory! But keep in mind — overusing layout effects can actually BLOCK paint and cause lag.
Here is the possible sudo code:
function ClampTitle() { const ref = React.useRef(null); const [clamp, setClamp] = React.useState(false); React.useLayoutEffect(() => { const { scrollWidth, clientWidth } = ref.current; setClamp(scrollWidth > clientWidth); }, []); return ( <h1 ref={ref} className={clamp ? "line-clamp-1" : ""}> A very long title </h1> ); }
Try testing by slowing font loads (I use Chrome DevTools to slow 3G). If your big title is clamped right away, you did it right. For ongoing resizes, add ResizeObserver, since font metrics and containers shift live. That way, you clamp correctly from the first paint.
5. Tooltip or Popover That Lands in the Right Spot Relative to a Button
Tooltips are tiny but tricky. On first render, they often show up in the wrong spot (usually top-left or center). Then, a frame later, they snap near the <anchor/>. That jump looks sloppy and, yeah, users notice!
With useLayoutEffect vs useEffect:
- useEffect hook runs after paint, so you risk that ugly “wrong spot for a frame.”
- useLayoutEffect hook runs right before paint, so you can measure and place the tooltip in time (no lag).
You read the anchor’s position (say, with getBoundingClientRect function) and calculate top and left values. Include scroll offsets so it doesn’t drift when the page is scrolled.
Accessibility matters too: tooltips need role=”tooltip”, aria-describedby, and should open with hover/focus. Don’t forget testing — the tooltip should land in the correct spot at t=first frame.
Here’s a simple sudo version. To skip collision math—use a popper lib if you want smart edges.
function Tooltip({ anchorRef, text }) { const [pos, setPos] = React.useState({ top: 0, left: 0 }); React.useLayoutEffect(() => { const r = anchorRef.current.getBoundingClientRect(); setPos({ top: r.bottom + window.scrollY, left: r.left + window.scrollX }); }, [anchorRef]); return <div role="tooltip" style={{ position: "absolute", ...pos }}>{text}</div>; }
React’s own tooltip patterns use this exact play!
6. Smooth Accordion — Measure Natural Height, Then Expand/Collapse
An accordion snapping opening can go wrong because CSS transitions are going from height: 0 to height: auto. It just skips and looks super clunky. To fix it, we can FIRST measure the natural height, THEN feed that into a transition!
Measure in useEffect, you’ll see that first-frame “pop” – the browser paints before you know the right height. With the useLayoutEffect hook, you grab scrollHeight before paint, then smoothly animate—no jump. Try both—one feels polished, the other feels buggy.
Sudeo code:
function Accordion({ open, children }) { const ref = React.useRef(null); const [h, setH] = React.useState(0); React.useLayoutEffect(() => { setH(open ? ref.current.scrollHeight : 0); }, [open]); return ( <div style={{ overflow: "hidden", height: h, transition: "height 200ms" }}> <div ref={ref}>{children}</div> </div> ); }
So keep layout work tight—just measure, set height, and let CSS animation handle the pretty part. And don’t ignore keyboard shortcuts or users using prefers-reduced-motion — do not enforce them through animations.
7. Keep the Caret Selection Stable While and After Re-Rendering
Typing feels super weird when your caret suddenly jumps. This happens if React re-renders your input and the caret resets after paint. Why? Because useEffect() restores the selection too late. You see a visible flicker. That’s where using useLayoutEffect vs useEffect makes a huge difference.
The fix is simple — snapshot the caret position right before React paints again, then restore it instantly. useLayoutEffect() lets you do this pre-paint, so the user NEVER notices anything. In contrast, useEffect() runs post-paint, so you get a noticeable reset. Basically, layout effect says “restore it now,” effect says “restore it later”.
Sudo code example:
function KeepCaret() { const ref = React.useRef(null); React.useLayoutEffect(() => { const el = ref.current; const pos = el.selectionStart; return () => el.setSelectionRange(pos, pos); }); return <input ref={ref} defaultValue="Edit me" />; }
When testing, simulate fast typing with rerenders — like typing in a live-search input. If the caret feels anchored, you did it right. Just don’t interfere with ongoing IME composition events. This keeps typing smooth, predictable, and stable under state updates.
When useEffect is the Better Choice — And Why
If you’re weighing useLayoutEffect vs useEffect, most of the time, React docs (the official words) say to prefer useEffect. Why? Because it doesn’t block paint — meaning your UI renders smoothly before the effect logic kicks in. Think of it as “do the non-visual chores later”.
Here are the canonical cases where useEffect is better:
- Data fetching – Network calls don’t touch layout (let them run async, post-render).
- Subscriptions – WebSocket or event listeners (add/remove safely without blocking the paint).
- Analytics – Tracking users (GA calls, custom logs) happens outside the layout.
- Timers – setTimeout or setInterval (these don’t affect pre-paint flow). Check my post about “JavaScript Sleep Function — Best 7 Examples To Delay, Pause, and Revoke JS Code“
- Debounce – Controlled search inputs or scroll events (trigger logic after user stops).
- Document title/meta updates – Changing tab titles or meta tags (visual isn’t blocked).
- Storage – LocalStorage/sessionStorage saves (sync data safely after render).
- Preloading assets – Load images, preload fonts (async work, no blocking paint).
- URL/history sync – Push state or update query params (browser handles visuals already).
Why it fits
All of the above share one rule: they DO NOT mess with DOM measurements or styles before the screen draws. They’re not layout mutations. They just “do work on the side” or resolve side effects.
If you run these inside useLayoutEffect, React will PAUSE painting until your code finishes. That leads to annoying lags. So yeah — don’t do it.
WARNING: Don’t mutate layout inside useEffect. If you’re doing something like measuring DOM nodes, tweaking CSS, shifting elements around — that’s layout work. It belongs in useLayoutEffect — that’s the only time you want to block paint briefly.
So, the rule of thumb:
- useEffect = background jobs (safe, async, doesn’t block).
- useLayoutEffect = layout fix hacks (only if you must).
Table: Fast Compare useLayoutEffect vs useEffect Hooks
And remember, sometimes you don’t even need an effect. React team reminds us — if you can derive state from props, or compute it inline, skip the effect entirely. Overusing effects makes code messy.
Conclusion — When to Reach for useLayoutEffect vs useEffect
Default to useEffect — Only grab useLayoutEffect if you need to stop a visual glitch or measure something before the browser paints.
- Measure, position, or focus elements → useLayoutEffect (blocky UI fixed fast).
- Data fetch, analytics, timers, or side logging → useEffect (lighter, async vibe).
- Small reminder: layout effects block paint—keep them tiny (SSR + StrictMode might double-run).
Prefer useEffect. Measure only when the real UI jumps bug the eye. Profile both.
If you are learning web development, check my post about “What Is JavaScript Used For with 8 Powerful Reasons to Know About JS“!