React Hooks review #9: useLayoutEffect

Two more to go in the react hooks series. The goal was simple: summarize all the eleven official React hooks.

useLayoutEffect

The signature is identical to useEffect, but it fires synchronously after all DOM mutations.

React.js

Syntax:

useLayoutEffect(() => {
  // Run side effects
  return () => // cleanup 
}, [dependencyArray]);
  • ref: ref to the sub-component.
  • createHandle: object returned from the sub-component to the parent.

useEffect & useLayoutEffect lifecycle

Image from the cool interactive page https://wavez.github.io/react-hooks-lifecycle/

Or in my messy graph:

Once more, the same lifecycle shown thing with console logs:

const EffectFlow = ({counter}) => {
    console.log('render');
    const [effectCounter,setEffectCounter] = useState(0);

    useLayoutEffect(() => {
        console.log('useLayoutEffect');
    }, [counter]);

    useEffect(() => {
        console.log('useEffect');
    }, [counter]);

    return <>
        <p>Sub-counter: {counter}</p>
    </>
}

// The logs...
//   render
//   useLayoutEffect
//   useEffect

A visual example

The right time to useLayoutEffect instead? You’ll know it when you see it. Literally 😉

When to useLayoutEffect Instead of useEffect (example)

The first use case is if your component is flickering when it updated. The reason this solution it’s the last resort is since the flickering means that there is unnecessary re-rendering on the first state change:

In this example there are two buttons. The first trigger useEffect. The second triggers useLayoutEffect. Both change the state before and inside the effect. While useLayoutEffect works smoothly, on the useEffect you can spot that the screen is flickering.

const App = () => {
    const [triggerUseEffect,setTriggerUseEffect] = useState({});
    const [triggerUseLayoutEffect,setTriggerUseLayoutEffect] = useState({});
    const [value, setValue] = useState(0);

    useLayoutEffect(() => {
        setValue(Math.random());
    }, [triggerUseLayoutEffect]);

    useEffect(() => {
        setValue(Math.random());
    }, [triggerUseEffect]);

    return (
        <>
            <div>{value} </div>
            <button onClick={()=>{setValue('--- loading ---');setTriggerUseEffect({})}}>Trigger useEffect</button>
            <button onClick={()=>{setValue('--- loading ---');setTriggerUseLayoutEffect({})}}>Trigger useLayoutEffect</button>
        </>
    );
};

A clearer example with a task that takes a second to complete. This time it’s not a flick, but a “loading” message. But it makes the concept clear.

const App = () => {
    const [triggerUseEffect,setTriggerUseEffect] = useState({});
    const [triggerUseLayoutEffect,setTriggerUseLayoutEffect] = useState({});
    const [value, setValue] = useState(0);

    useLayoutEffect(() => {
        setValue(Math.random());
        waitASec();
    }, [triggerUseLayoutEffect]);

    useEffect(() => {
        setValue(Math.random());
        waitASec();
    }, [triggerUseEffect]);

    return (
        <>
            <div>{value} </div>
            <button onClick={()=>{setValue('--- loading ---');setTriggerUseEffect({})}}>Trigger useEffect</button>
            <button onClick={()=>{setValue('--- loading ---');setTriggerUseLayoutEffect({})}}>Trigger useLayoutEffect</button>
        </>
    );
};

The first will show “loading” for a second, the change to the value. The second button will wait for a second and then update the value. This is very useful if you want the layout to change only once, or you start some visual effect while the value is ready.

If you need to trigger fast

Since useLayoutEffect executes way before the useEffect, It can be used for cases any mili-sec counts for value calculation. Like when it’s critical to sent a request to the server ASAP.

An example to show how far behind is the useEffect, using the good old console.time I reviewed long time ago:

const UseLayoutEffect = ({counter}) => {
    console.time("UseLayoutEffect timer");
    console.timeLog("UseLayoutEffect timer",'Render');
    const [effectCounter,setEffectCounter] = useState(0);

    useLayoutEffect(() => {
        console.timeLog("UseLayoutEffect timer",'Use layout effect');
    }, [counter]);

    useEffect(() => {
        console.timeLog("UseLayoutEffect timer",'Use effect');
    }, [counter]);

    return <>
        <p>Sub-counter: {counter}</p>
    </>
}

// UseLayoutEffect timer: 0.003173828125 ms Render
// UseLayoutEffect timer: 0.780029296875 ms Render
// UseLayoutEffect timer: 5.671875 ms Use layout effect
// UseLayoutEffect timer: 9.39990234375 ms Use effect

Conclusion

After all the good words, remember:

Allow me to preface this answer by stating that all of these hooks are very rarely used. 99% of the time, you won’t need these. They are only meant to cover some rare corner-case scenarios.

When to use useImperativeHandle, useLayoutEffect, and useDebugValue