Have you ever built a tooltip that snaps from the top-left corner to its correct spot? Or a modal that flashes at the wrong size before settling into place? That split-second glitch is a layout flicker. It happens when React reads the DOM, calculates a correction, and updates state, but the browser has already started pushing pixels to the screen. The usual prescription is to swap useEffect for useLayoutEffect. The swap works, but only if you understand exactly when each hook fires inside the browser pipeline.

The Browser Pipeline: Render, Commit, Paint

React updates a component in three distinct stages. In the render phase, React builds—or rebuilds—the Virtual DOM and computes the diff. No actual pixel changes yet; this is pure calculation happening in memory. Next comes the commit phase, where React applies those changes to the real DOM nodes. Styles update, nodes are inserted or removed, and text changes.

Then the browser takes over. In the paint phase, the browser’s rendering engine calculates layout geometry and draws pixels onto the screen. This sequence is rigid. The browser must complete layout before it can paint, and it must finish painting before the user sees anything new. The gap between commit and paint is measured in milliseconds, but it is real, and it is the window where useEffect and useLayoutEffect diverge.

Why useEffect Causes the Flicker

useEffect runs asynchronously, scheduled to fire after the browser has already painted the screen. The DOM is updated, the pixels are drawn, and then React steps back in to run your effect.

Imagine you render a dropdown menu under a button. Inside useEffect, you call buttonRef.current.getBoundingClientRect(), compute the correct top and left coordinates, and store them in state. Because useEffect runs after paint, the browser has already drawn the dropdown at its default position, perhaps at top: 0, left: 0. Only after that paint does your effect update state. React commits the corrected coordinates, and the browser paints again. The user sees two frames: the wrong position, then the right one. That visual snap is the flicker everyone tries to avoid.

For data fetching, API calls, analytics tracking, or setting up event listeners, this delay does not matter. The user does not care whether an analytics beacon fires a few milliseconds after the paint. In fact, pushing non-visual work until after paint keeps the initial render responsive. But for layout-dependent corrections, useEffect is simply too late.

How useLayoutEffect Blocks the Paint

useLayoutEffect runs synchronously, immediately after React mutates the DOM but before the browser has a chance to calculate layout or paint pixels. It blocks the paint pipeline entirely.

If you perform the same dropdown measurement inside useLayoutEffect, the sequence changes. React commits the initial DOM update, runs your layout effect, and your state update triggers a synchronous re-render. React commits the corrected coordinates, and only then does the browser paint. The user sees one frame, already correct.

That blocking behavior is both the feature and the risk. Because useLayoutEffect prevents the browser from painting until it finishes, any heavy computation inside it freezes the UI. Even a few dozen milliseconds of blocked paint feels like jank to the user. This is why the React docs explicitly tell you to start with useEffect and only promote to useLayoutEffect when you actually observe a flicker you cannot tolerate.

When to Use Each Hook

Most of your logic belongs in useEffect. Use it for:

  • Fetching data from an API
  • Setting up subscriptions or event listeners
  • Sending analytics events
  • Any side effect that does not read or mutate layout immediately

Reserve useLayoutEffect for operations that must read the DOM and write back before the user sees the frame:

  • Measuring element dimensions, such as width, height, or scroll position
  • Calculating coordinates for tooltips, popovers, or context menus
  • Preventing visible layout shifts when the visual position depends on rendered geometry

If you are unsure which to choose, default to useEffect. Move to useLayoutEffect only when you notice visual instability. This rule alone will keep the vast majority of React applications running smoothly.

The Server-Side Rendering Gotcha

Next.js, Remix veya React'ı sunucuda render eden herhangi bir framework kullanıyorsanız, useLayoutEffect ile bir uyarı alırsınız. Sunucuda DOM bulunmadığı için, hook'un ölçecek bir şeyi yoktur. React sizi, bir tarayıcı ortamı beklediği ancak bulamadığı konusunda uyarır. Hydration sırasında bu uyumsuzluk, sunucuda render edilen işaretleme ile istemcinin ilk hedeflediği render farklı olabileceği için ince hatalara yol açabilir.

Standart çözüm, ortama bağlı olarak doğru effect'i seçen izomorfik bir hook kullanmaktır:

const useIsomorphicLayoutEffect =
  typeof window !== 'undefined' ? useLayoutEffect : useEffect;

Bu wrapper'ı, DOM düğümlerini ölçmesi gereken ancak sunucu render'ı sırasında çalışabilecek tüm bileşenlerde kullanın. Bu, uyarıyı susturur ve sunucu çıktınızın tutarlı kalmasını sağlar.

Performans ve En İyi Uygulamalar

useLayoutEffect boyama (painting) işlemini engellediği için, hook'un gövdesini mümkün olduğunca hafif tutun. Layout değerini okuyun, düzeltmeyi hesaplayın ve geri yazın. İçerisinde veri çekmeyin, büyük nesneleri ayrıştırmayın veya maliyetli algoritmalar çalıştırmayın. Buradaki ağır kodlar ana iş parçacığını (main thread) durduracak ve arayüzünüzün donmuş gibi hissettirmesine neden olacaktır.

Elementleri ölçerken document.getElementById yerine React ref'lerini kullanın. Ref'ler bileşen örneğinize (instance) bağlıdır, sorgu hilelerine gerek duymadan yeniden render işlemlerinden sağ çıkar ve portal'lar veya koşullu render'lar ile güvenilir bir şekilde çalışır. Global ID aramaları bileşen kapsüllemesini (encapsulation) bozar ve tam onlara ihtiyaç duyduğunuz anda null döndürebilir.

Neredeyse her yan etki (side effect) için doğru varsayılan useEffect'tir. Tarayıcının kesintisiz bir şekilde boyama yapmasına olanak tanır; verileri, olayları ve harici senkronizasyonu temiz bir şekilde yönetir. useLayoutEffect, belirli bir sorun için özelleşmiş bir araçtır: boyama işleminden önce layout'u okumak ve geri yazmak. Aralarındaki zamanlama farkında ustalaşırsanız, titremelerin (flickers) peşinden koşmayı bırakıp onları önlemeye başlarsınız.

Asıl çıkarılması gereken ders: Her şey için useEffect ile başlayın. Bir tooltip veya modal'ın kendini düzeltmeden önce yanlış bir yerde yanıp söndüğünü gördüğünüz an, bu sizin sinyalinizdir. useLayoutEffect'e geçin, DOM'u ölçün, layout'unuzu ayarlayın ve tarayıcının tek bir seferde —doğru bir şekilde— boyama yapmasına izin verin.