useInsertionEffect — React Hook

AkashSDas
3 min readMar 27, 2024

--

useInsertionEffect hook allow us to insert elements into the DOM before any layout Effects fire. Its meant for CSS-in-JS library authors, and for app authors, we should be using useEffect and useLayoutEffect.

Following is the signature of useInsertionEffect:

useInsertionEffect(setup, dependencies?)

setup is a function which contains our effect’s logic and it can optionally return another function i.e. for cleanup.

When our component gets added to the DOM but before any layout Effect fire, React will run our setup function. After every re-render with changed dependencies, React will first run the cleanup function (if provided) with the old values, and then run the setup function with new values. After our component is removed from the DOM, React will run the cleanup function.

dependencies (optional) is a list of all reactive values, used inside the setup function. Reactive values include props, state, and all the variables and functions declared directly inside our component body. React will compare each dependency with its previous value using the Object.is comparison.

The list of dependencies must have a constant number of items and be written inline. If we omit this then our Effect will re-run after every re-render of the component.

Here is an example that showcases when useInsertionEffect event occurs as compared to useLayoutEffect and useEffect:

import {
useEffect,
useInsertionEffect,
useLayoutEffect,
useState,
} from "react";

export default function App(): JSX.Element {
const [show, setShow] = useState(false);

return (
<div>
<button onClick={() => setShow(!show)}>Toggle</button>
{show && <Child />}
{show && <Child2 />}
</div>
);
}

function Child(): JSX.Element {
const [show, setShow] = useState(false);

useInsertionEffect(function () {
console.log("[insertion 1]");

return function () {
console.log("[destruction 1]");
};
});

useInsertionEffect(function () {
console.log("[insertion 11]");

return function () {
console.log("[destruction 11]");
};
});

useLayoutEffect(function () {
console.log("[effect 1]");

return function () {
console.log("[effect cleanup 1]");
};
});

useLayoutEffect(function () {
console.log("[effect 11]");

return function () {
console.log("[effect cleanup 11]");
};
});

useEffect(function () {
console.log("[*effect 1]");

return function () {
console.log("[*effect cleanup 1]");
};
});

return (
<div>
<h2>Child</h2>
<button onClick={() => setShow(!show)}>Toggle</button>
</div>
);
}

function Child2(): JSX.Element {
const [show, setShow] = useState(false);

useInsertionEffect(function () {
console.log("[insertion 2]");

return function () {
console.log("[destruction 2]");
};
});

useLayoutEffect(function () {
console.log("[effect 2]");

return function () {
console.log("[effect cleanup 2]");
};
});

return (
<div>
<h2>Child</h2>
<button onClick={() => setShow(!show)}>Toggle</button>
</div>
);
}

// Output after Toggle btn is clicked on initial render:
// [insertion 1]
// [insertion 11]
// [insertion 2]
// [effect 1]
// [effect 11]
// [effect 2]
// [*effect 1]
// [effect cleanup 1]
// [effect cleanup 11]
// [effect cleanup 2]
// [*effect cleanup 1]
// [effect 1]
// [effect 11]
// [effect 2]
// [*effect 1]

🚨 In Strict Mode (development), React will run one extra setup+cleanup cycle. Effects only run on the client. They don’t run during server rendering.

Key details we need to know while using useInsertionEffect:

  • We can’t update state from inside useInsertionEffect
  • By the time useInsertionEffect runs, refs are not attached yet
  • useInsertionEffect may run either before or after the DOM has been updated. We shouldn’t rely on the DOM being updated at any particular time.
  • Unlike other types of Effects, which fire cleanup for every Effect and then setup for every Effect, useInsertionEffect will fire both cleanup and setup one component at a time. This results in an “interleaving” of the cleanup and setup functions.

--

--