useLayoutEffect – React (original) (raw)

Pitfall

useLayoutEffect can hurt performance. Prefer useEffect when possible.

useLayoutEffect is a version of useEffect that fires before the browser repaints the screen.


useLayoutEffect(setup, dependencies?)


Reference

useLayoutEffect(setup, dependencies?)

Call useLayoutEffect to perform the layout measurements before the browser repaints the screen:


import { useState, useRef, useLayoutEffect } from 'react';

function Tooltip() {

const ref = useRef(null);

const [tooltipHeight, setTooltipHeight] = useState(0);

useLayoutEffect(() => {

const { height } = ref.current.getBoundingClientRect();

setTooltipHeight(height);

}, []);

// ...

See more examples below.

Parameters

Returns

useLayoutEffect returns undefined.

Caveats


Usage

Measuring layout before the browser repaints the screen

Most components don’t need to know their position and size on the screen to decide what to render. They only return some JSX. Then the browser calculates their layout (position and size) and repaints the screen.

Sometimes, that’s not enough. Imagine a tooltip that appears next to some element on hover. If there’s enough space, the tooltip should appear above the element, but if it doesn’t fit, it should appear below. In order to render the tooltip at the right final position, you need to know its height (i.e. whether it fits at the top).

To do this, you need to render in two passes:

  1. Render the tooltip anywhere (even with a wrong position).
  2. Measure its height and decide where to place the tooltip.
  3. Render the tooltip again in the correct place.

All of this needs to happen before the browser repaints the screen. You don’t want the user to see the tooltip moving. Call useLayoutEffect to perform the layout measurements before the browser repaints the screen:


function Tooltip() {

const ref = useRef(null);

const [tooltipHeight, setTooltipHeight] = useState(0); // You don't know real height yet

useLayoutEffect(() => {

const { height } = ref.current.getBoundingClientRect();

setTooltipHeight(height); // Re-render now that you know the real height

}, []);

// ...use tooltipHeight in the rendering logic below...

}

Here’s how this works step by step:

  1. Tooltip renders with the initial tooltipHeight = 0 (so the tooltip may be wrongly positioned).
  2. React places it in the DOM and runs the code in useLayoutEffect.
  3. Your useLayoutEffect measures the height of the tooltip content and triggers an immediate re-render.
  4. Tooltip renders again with the real tooltipHeight (so the tooltip is correctly positioned).
  5. React updates it in the DOM, and the browser finally displays the tooltip.

Hover over the buttons below and see how the tooltip adjusts its position depending on whether it fits:

import { useRef, useLayoutEffect, useState } from 'react'; import { createPortal } from 'react-dom'; import TooltipContainer from './TooltipContainer.js';

export default function Tooltip({ children, targetRect }) { const ref = useRef(null); const [tooltipHeight, setTooltipHeight] = useState(0);

useLayoutEffect(() => { const { height } = ref.current.getBoundingClientRect(); setTooltipHeight(height); console.log('Measured tooltip height: ' + height); }, []);

let tooltipX = 0; let tooltipY = 0; if (targetRect !== null) { tooltipX = targetRect.left; tooltipY = targetRect.top - tooltipHeight; if (tooltipY < 0) {

  tooltipY = targetRect.bottom;
}

}

return createPortal( {children} , document.body ); }

Notice that even though the Tooltip component has to render in two passes (first, with tooltipHeight initialized to 0 and then with the real measured height), you only see the final result. This is why you need useLayoutEffect instead of useEffect for this example. Let’s look at the difference in detail below.

useLayoutEffect vs useEffect

useLayoutEffect blocks the browser from repainting

React guarantees that the code inside useLayoutEffect and any state updates scheduled inside it will be processed before the browser repaints the screen. This lets you render the tooltip, measure it, and re-render the tooltip again without the user noticing the first extra render. In other words, useLayoutEffect blocks the browser from painting.

import { useRef, useLayoutEffect, useState } from 'react'; import { createPortal } from 'react-dom'; import TooltipContainer from './TooltipContainer.js';

export default function Tooltip({ children, targetRect }) { const ref = useRef(null); const [tooltipHeight, setTooltipHeight] = useState(0);

useLayoutEffect(() => { const { height } = ref.current.getBoundingClientRect(); setTooltipHeight(height); }, []);

let tooltipX = 0; let tooltipY = 0; if (targetRect !== null) { tooltipX = targetRect.left; tooltipY = targetRect.top - tooltipHeight; if (tooltipY < 0) {

  tooltipY = targetRect.bottom;
}

}

return createPortal( {children} , document.body ); }

Note

Rendering in two passes and blocking the browser hurts performance. Try to avoid this when you can.


Troubleshooting

I’m getting an error: “useLayoutEffect does nothing on the server”

The purpose of useLayoutEffect is to let your component use layout information for rendering:

  1. Render the initial content.
  2. Measure the layout before the browser repaints the screen.
  3. Render the final content using the layout information you’ve read.

When you or your framework uses server rendering, your React app renders to HTML on the server for the initial render. This lets you show the initial HTML before the JavaScript code loads.

The problem is that on the server, there is no layout information.

In the earlier example, the useLayoutEffect call in the Tooltip component lets it position itself correctly (either above or below content) depending on the content height. If you tried to render Tooltip as a part of the initial server HTML, this would be impossible to determine. On the server, there is no layout yet! So, even if you rendered it on the server, its position would “jump” on the client after the JavaScript loads and runs.

Usually, components that rely on layout information don’t need to render on the server anyway. For example, it probably doesn’t make sense to show a Tooltip during the initial render. It is triggered by a client interaction.

However, if you’re running into this problem, you have a few different options: