Effects & Lifecycle

useInsertionEffect

Fires synchronously before any DOM mutations. Designed exclusively for CSS-in-JS libraries to inject styles before layout effects and effects read them.

Signature

TypeScript
useInsertionEffect(setup: () => (() => void) | void, dependencies?: any[])

Parameters

ParameterTypeDescription
setup() => (() => void) | voidA function that runs before DOM mutations. Typically used to inject <style> elements.
dependenciesany[]Optional dependency array controlling when the effect re-runs.

Return Value

undefined — useInsertionEffect does not return a value.

Examples

Injecting Dynamic Styles
import { useInsertionEffect } from 'react';

let styleCache = new Map<string, HTMLStyleElement>();

function useCSS(rule: string) {
  useInsertionEffect(() => {
    if (!styleCache.has(rule)) {
      const style = document.createElement('style');
      style.textContent = rule;
      document.head.appendChild(style);
      styleCache.set(rule, style);
    }
    return () => {
      const style = styleCache.get(rule);
      if (style) {
        style.remove();
        styleCache.delete(rule);
      }
    };
  }, [rule]);
}

function BlueBox() {
  useCSS('.blue-box { background: blue; padding: 16px; color: white; }');
  return <div className="blue-box">Styled with useInsertionEffect</div>;
}
Atomic CSS Generation
import { useInsertionEffect, useRef } from 'react';

const insertedRules = new Set<string>();

function useAtomicCSS(styles: Record<string, string>): string {
  const classNames: string[] = [];
  const rules: string[] = [];

  for (const [prop, value] of Object.entries(styles)) {
    const className = `_${prop}_${value}`.replace(/[^a-zA-Z0-9_]/g, '_');
    classNames.push(className);
    if (!insertedRules.has(className)) {
      rules.push(`.${className} { ${prop}: ${value}; }`);
    }
  }

  useInsertionEffect(() => {
    if (rules.length === 0) return;
    const style = document.createElement('style');
    style.textContent = rules.join('\n');
    document.head.appendChild(style);
    rules.forEach(r => insertedRules.add(r));
    return () => style.remove();
  }, [rules.join(',')]);

  return classNames.join(' ');
}
Theme CSS Variables
import { useInsertionEffect } from 'react';

function useThemeVariables(theme: Record<string, string>) {
  useInsertionEffect(() => {
    const style = document.createElement('style');
    const vars = Object.entries(theme)
      .map(([k, v]) => `  --${k}: ${v};`)
      .join('\n');
    style.textContent = `:root {\n${vars}\n}`;
    document.head.appendChild(style);
    return () => style.remove();
  }, [JSON.stringify(theme)]);
}

function App() {
  useThemeVariables({
    'color-primary': '#3b82f6',
    'color-bg': '#09090b',
    'color-text': '#fafafa',
  });
  return <div>App with injected CSS variables</div>;
}

Common Pitfalls

!

Using useInsertionEffect for anything other than style injection — it's not a general-purpose effect hook.

!

Reading DOM layout or refs inside useInsertionEffect — at this point the DOM hasn't been updated yet.

!

Not deduplicating style injections, leading to duplicate <style> elements.

!

Application code should almost never use this hook — it's designed for CSS-in-JS library authors.

Understanding useInsertionEffect

useInsertionEffect is the most specialized of React's three effect hooks, designed specifically for CSS-in-JS libraries like styled-components, Emotion, and Linaria. It fires before React applies any DOM mutations, giving style injection code a window to insert <style> elements before useLayoutEffect and useEffect run. This ensures that when layout effects measure the DOM, all computed styles are already applied.

The timing order of React's effect hooks is: useInsertionEffect → DOM mutations → useLayoutEffect → browser paint → useEffect. This makes useInsertionEffect the earliest point at which you can run code after a render. However, at this stage, refs are not yet attached and the DOM has not been updated, so you cannot read layout properties or interact with rendered elements.

For CSS-in-JS libraries, the previous approach was injecting styles during render (in the render function body) or in useLayoutEffect. Injecting during render violates React's rules about pure rendering, while useLayoutEffect was slightly too late — it runs after DOM mutations, meaning layout effects in other components might read incorrect styles. useInsertionEffect fills this gap perfectly.

Application developers should almost never use this hook directly. It's an escape hatch for library authors who need precise control over style injection timing. If you're building a component library or a styling system, useInsertionEffect ensures your styles are available before any component measures its layout. For everything else — data fetching, subscriptions, DOM manipulation — use useEffect or useLayoutEffect instead.

Related Hooks

More Effects & Lifecycle Hooks

Explore All React Hooks

Browse our complete reference of 19 React hooks with signatures, examples, pitfalls, and in-depth explanations.