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
*/