🧠 Understanding useLayoutEffect
: Fixing UI Flicker with Precise Layout Computation in React
When building interactive UIs, sometimes the layout of a component depends on the dimensions or position of another component. A common scenario is a tooltip that must appear either above or below a target element—based on how much space is available.
This kind of positioning logic might seem straightforward at first: just check the element’s position and update the state accordingly. But here’s where it gets tricky…
When we render a component, the browser updates the DOM. Only after that, we can measure things like width, height, or position using methods like getBoundingClientRect()
.
In many cases, developers use the useEffect
hook for this:
useEffect(() => {const rect = ref.current?.getBoundingClientRect()if (!rect) returnconst { height } = rectsetTooltipHeight(height)}, [])
This hook runs after the render is committed to the screen, so you’re measuring layout after the paint.
That means your component:
On low-end devices, this results in visible flickering—the user briefly sees the incorrect position before the correct one is applied.
useLayoutEffect
To prevent this flickering, we can use React’s useLayoutEffect
instead of useEffect
. It has the exact same API, but it runs synchronously after the DOM is updated and before the browser repaints the screen.
In other words:
useEffect
→ Runs after the paint.useLayoutEffect
→ Runs before the paint.By switching to useLayoutEffect
, your component can:
Here’s the updated code:
useLayoutEffect(() => {const rect = ref.current?.getBoundingClientRect()if (!rect) returnconst { height } = rectsetTooltipHeight(height)}, [])
Now, when a user hovers over an element (like a heart icon), the tooltip will appear in the right position the first time it renders—no awkward flickering!
In a recent test, one of our engineers added an artificial delay to simulate a low-end device scenario. On hover, the tooltip would briefly appear below the heart icon, then jump above it once layout measurements were complete.
This clearly illustrated the problem of using useEffect
for layout-sensitive components. The solution? Replacing useEffect
with useLayoutEffect
eliminated the flicker entirely.
useLayoutEffect
You should only use useLayoutEffect
when:
For all other side effects (like fetching data, setting up subscriptions, logging, etc.), useEffect
is still the preferred and more efficient choice.
useEffect
runs after paint, leading to potential flickers.useLayoutEffect
runs before paint, allowing you to make layout adjustments invisibly.useLayoutEffect
when layout measurements impact UI positioning on first render.With this knowledge, your UI will look smoother, feel faster, and behave more predictably—even on low-end devices.
Quick Links
Legal Stuff
Social Media