Two Hooks, One Job Description
React's useEffect and useLayoutEffect share a similar signature and can often be used interchangeably. The real distinction is timing: when their callbacks run relative to the browser's paint cycle. Choosing wrong can mean either a visible flicker or an unnecessary blocking of the render pipeline.
useEffect: The Default Choice
In nearly all cases, useEffect is what you want. When migrating class components to hooks, code from componentDidMount, componentDidUpdate, and componentWillUnmount typically maps into a single useEffect call. The key behavioral difference: while class lifecycle methods run synchronously right after rendering, useEffect fires after React has rendered and does not block the browser's painting. That makes it the more performant option for the vast majority of side effects like data fetching, subscriptions, or any logic that doesn't require immediate visual feedback.
useLayoutEffect: When Timing is Visual
useLayoutEffect runs synchronously right after React has committed all DOM mutations, but before the browser has a chance to repaint. This puts it on the same scheduling footing as componentDidMount and componentDidUpdate. The user won't see any intermediate state because the browser hasn't painted it yet.
Reach for it when you need to make DOM measurements—scroll position, element styles, or dimensions—and then immediately mutate the DOM or trigger a synchronous state update that causes a re-render. If you used useEffect for such a task, the mutation would happen after the browser paints the initial render, and the user could briefly see the pre-mutation UI. That flicker is exactly what useLayoutEffect prevents.
A Special Case: Updating Refs
There is one less obvious scenario where useLayoutEffect beats useEffect: updating a ref where you need the new value to be visible to any subsequently executed code. With useEffect, other hooks or events triggered in the same commit might still see the stale ref value:
const ref = React.useRef()
React.useEffect(() => {
ref.current = 'some value'
})
// then, later in another hook or something
React.useLayoutEffect(() => {
console.log(ref.current) // <-- this logs an old value because this runs first!
})
In this situation, using useLayoutEffect guarantees the ref is current before any other code in the same commit runs.
Which One Should You Pick?
- useLayoutEffect: Your code mutates the DOM in an observable way, or you need to take measurements and act on them before the browser paints.
- useEffect: Your side effect does not touch the DOM, or the DOM changes are invisible to the user. This is the right default for nearly everything.
The rule of thumb is to start from the default—letting the browser repaint before your code runs. That keeps your application responsive and displays updates sooner. Only deviate when the specific timing requirements of your effect demand it.



