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.
- The three basic Hooks:
useState
,useEffect
,useContext
. - The seven additional Hooks:
useReducer
,useCallback
,useMemo
,useRef
,useImperativeHandle
,useLayoutEffect
,useDebugValue
. - And understand the endless potential of custom hooks.
useLayoutEffect
The signature is identical to
React.jsuseEffect
, but it fires synchronously after all DOM mutations.
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
When to useLayoutEffect Instead of useEffect (example)useLayoutEffect
instead? You’ll know it when you see it. Literally 😉
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