How to Test useLayoutEffect Hook in React

Testing the useLayoutEffect hook in React can be challenging because it fires synchronously after all DOM mutations but before the browser paints. This article provides a clear, step-by-step guide on how to test components using useLayoutEffect using React Testing Library and Jest, including how to mock DOM measurements and avoid common environment pitfalls.

Understanding the Testing Challenge

Unlike useEffect, which runs asynchronously, useLayoutEffect runs synchronously. It is typically used to read layout from the DOM and synchronously re-render.

Because test runners like Jest use jsdom as a simulated browser environment, layout engines are not fully implemented. This means DOM elements do not have actual dimensions (e.g., offsetWidth or getBoundingClientRect() will return 0). To test useLayoutEffect behavior that relies on DOM measurements, you must mock these DOM APIs in your tests.

Step-by-Step Testing Guide

1. Create the Component

Consider a component that measures its own width using useLayoutEffect and displays it:

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

export function StandardBox() {
  const boxRef = useRef(null);
  const [width, setWidth] = useState(0);

  useLayoutEffect(() => {
    if (boxRef.current) {
      const rect = boxRef.current.getBoundingClientRect();
      setWidth(rect.width);
    }
  }, []);

  return (
    <div ref={boxRef} data-testid="box-element">
      Width is {width}px
    </div>
  );
}

2. Write the Test and Mock the DOM API

To test this component, you need to mock getBoundingClientRect on the HTMLElement prototype before rendering the component.

Here is the complete test file using Jest and React Testing Library:

import React from 'react';
import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom';
import { StandardBox } from './StandardBox';

describe('StandardBox Component', () => {
  let originalGetBoundingClientRect;

  beforeAll(() => {
    // Keep a reference to the original method
    originalGetBoundingClientRect = HTMLElement.prototype.getBoundingClientRect;
  });

  afterAll(() => {
    // Restore the original method after all tests run
    HTMLElement.prototype.getBoundingClientRect = originalGetBoundingClientRect;
  });

  it('should measure and display the correct element width', () => {
    // Mock the getBoundingClientRect method to return custom dimensions
    HTMLElement.prototype.getBoundingClientRect = jest.fn(() => ({
      width: 250,
      height: 100,
      top: 0,
      left: 0,
      bottom: 0,
      right: 0,
    }));

    render(<StandardBox />);

    // Assert that the width updated via useLayoutEffect is rendered
    const boxElement = screen.getByTestId('box-element');
    expect(boxElement).toHaveTextContent('Width is 250px');
  });
});

3. Handling Server-Side Rendering (SSR) Warnings

If you run tests in an environment where window is not defined (such as Node.js without jsdom), React will output a warning: “useLayoutEffect does nothing on the server…”.

React Testing Library configures jsdom by default, so this warning rarely appears. However, if you encounter this warning in your test suite, you can temporarily suppress it by mocking the console or running your tests in a proper jsdom environment by adding the following comment at the top of your test file:

/**
 * @jest-environment jsdom
 */