Why Use useInsertionEffect in React
In this article, we explore React’s useInsertionEffect
hook, explaining its purpose, how it differs from other effect hooks,
and why developers should use it. We will look at its primary use
case—injecting dynamic styles in CSS-in-JS libraries—and how it solves
performance issues related to layout shifts and style
recalculations.
What is useInsertionEffect?
Introduced in React 18, useInsertionEffect is a
specialized hook designed specifically for CSS-in-JS library authors. It
has the same signature as useEffect and
useLayoutEffect, but it fires at a very specific point in
the React lifecycle: before any DOM mutations and before layout
effects run.
Because it fires so early, it allows developers to inject
<style> tags or dynamic CSS rules into the document
before the browser calculates the layout of the page.
The Problem with Traditional Effect Hooks
To understand why useInsertionEffect is necessary, it
helps to look at how other hooks handle style injection:
useEffect: This hook runs asynchronously after the browser has already painted the screen. If you inject a<style>tag here, the browser has to recalculate the styles and repaint the screen, causing a visible flash of unstyled content (FOUC) and layout thrashing.useLayoutEffect: This hook runs synchronously after DOM mutations but before the browser paints. While this prevents the visual flash, it still forces the browser to recalculate layouts. If a layout effect reads the layout (e.g., measuring an element’s height withgetBoundingClientRect), and a style is injected at the same time, the browser is forced to perform expensive, redundant style recalculations.
Why Developers Should Use useInsertionEffect
If you are building or maintaining a CSS-in-JS library, or need to
inject global styles dynamically, useInsertionEffect offers
major advantages:
1. Eliminates Performance Bottlenecks
By injecting <style> tags before the browser
calculates layouts, useInsertionEffect prevents the browser
from having to recalculate styles multiple times during a single frame.
This solves the “layout thrashing” issue, making your application feel
faster and reducing CPU usage.
2. Ensures Accurate Layout Measurements
If you use useLayoutEffect to measure the size or
position of a DOM element, you need to be certain that the element’s
final styles have already been applied. Because
useInsertionEffect runs before
useLayoutEffect, it guarantees that all dynamic styles are
already active when your layout measurements occur.
3. Safer Concurrent Rendering
With React 18’s concurrent rendering features, components can yield
control back to the browser during rendering. Injecting styles during
the render phase itself is unsafe and can lead to bugs.
useInsertionEffect provides a safe, timed hook to perform
these mutations without interrupting the concurrent rendering flow.
How to Use It
The syntax for useInsertionEffect is identical to
useEffect. Here is a basic example of how a CSS-in-JS
library might utilize it:
import { useInsertionEffect } from 'react';
function useDynamicStyle(rule) {
useInsertionEffect(() => {
// 1. Create the style tag
const style = document.createElement('style');
style.textContent = rule;
// 2. Insert it into the document head before layout calculations
document.head.appendChild(style);
// 3. Clean up the style tag when the component unmounts
return () => {
document.head.removeChild(style);
};
}, [rule]);
}When Not to Use It
useInsertionEffect has a highly specific use case. You
should not use it for: * Fetching data. * Subscribing
to global events. * Updating state variables. * Triggering
animations.
For standard side effects, continue to use useEffect.
For measuring layouts or scheduling visual updates, use
useLayoutEffect. Save useInsertionEffect
strictly for injecting dynamic CSS rules into the DOM.