useEffect and useLayoutEffect are two hooks in React for working with side effects, but they execute at different times in the component lifecycle:
Characteristic | useEffect | useLayoutEffect |
---|---|---|
Execution time | Asynchronously, after rendering | Synchronously, before rendering |
Blocks rendering | No | Yes |
Visible to user | May cause flickering | No flickering |
Performance | Better | Worse |
When to use:
The useEffect and useLayoutEffect hooks in React solve similar tasks but have key differences in execution time and performance impact.
Both hooks allow side effects in functional components but differ in execution timing:
import { useEffect, useLayoutEffect, useState } from 'react';
function Component() {
const [count, setCount] = useState(0);
// Executes asynchronously after rendering
useEffect(() => {
console.log('useEffect: Component rendered');
});
// Executes synchronously before rendering
useLayoutEffect(() => {
console.log('useLayoutEffect: Before rendering');
});
return <div>Counter: {count}</div>;
}
useEffect executes asynchronously after rendering:
function WithUseEffect() {
const [count, setCount] = useState(0);
useEffect(() => {
// Executes after rendering
console.log('useEffect executed');
document.title = `Counter: ${count}`;
}, [count]);
return <div>Counter: {count}</div>;
}
useLayoutEffect executes synchronously before rendering:
function WithUseLayoutEffect() {
const [count, setCount] = useState(0);
useLayoutEffect(() => {
// Executes before rendering
console.log('useLayoutEffect executed');
document.title = `Counter: ${count}`;
}, [count]);
return <div>Counter: {count}</div>;
}
useEffect does not block rendering:
function NonBlockingEffect() {
const [width, setWidth] = useState(0);
useEffect(() => {
// Does not block rendering
const calculateWidth = () => {
// Heavy calculations
let result = 0;
for (let i = 0; i < 1000000; i++) {
result += i;
}
setWidth(result % 1000);
};
calculateWidth();
}, []);
return <div>Width: {width}px</div>;
}
useLayoutEffect blocks rendering:
function BlockingEffect() {
const [width, setWidth] = useState(0);
useLayoutEffect(() => {
// Blocks rendering until completion
const calculateWidth = () => {
// Heavy calculations
let result = 0;
for (let i = 0; i < 1000000; i++) {
result += i;
}
setWidth(result % 1000);
};
calculateWidth();
}, []);
return <div>Width: {width}px</div>;
}
useEffect may cause flickering:
function FlickeringComponent() {
const [isVisible, setIsVisible] = useState(false);
useEffect(() => {
// May cause flickering
if (isVisible) {
document.body.style.backgroundColor = 'yellow';
} else {
document.body.style.backgroundColor = 'white';
}
}, [isVisible]);
return (
<div>
<p>Background may flicker</p>
<button onClick={() => setIsVisible(!isVisible)}>
Toggle background
</button>
</div>
);
}
useLayoutEffect does not cause flickering:
function NonFlickeringComponent() {
const [isVisible, setIsVisible] = useState(false);
useLayoutEffect(() => {
// Does not cause flickering
if (isVisible) {
document.body.style.backgroundColor = 'yellow';
} else {
document.body.style.backgroundColor = 'white';
}
// Cleanup
return () => {
document.body.style.backgroundColor = 'white';
};
}, [isVisible]);
return (
<div>
<p>Background does not flicker</p>
<button onClick={() => setIsVisible(!isVisible)}>
Toggle background
</button>
</div>
);
}
// Loading data from API
useEffect(() => {
async function fetchData() {
const response = await fetch('/api/data');
const data = await response.json();
setData(data);
}
fetchData();
}, []);
// Event subscription
useEffect(() => {
function handleResize() {
setWindowSize({
width: window.innerWidth,
height: window.innerHeight
});
}
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
// Measuring element dimensions
useLayoutEffect(() => {
const element = ref.current;
if (element) {
setDimensions({
width: element.offsetWidth,
height: element.offsetHeight
});
}
}, []);
// Scroll synchronization
useLayoutEffect(() => {
const savedPosition = localStorage.getItem('scrollPosition');
if (savedPosition) {
window.scrollTo(0, parseInt(savedPosition));
}
}, []);
import { useLayoutEffect, useRef, useState } from 'react';
function ElementMeasurer() {
const ref = useRef();
const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
useLayoutEffect(() => {
if (ref.current) {
const { offsetWidth, offsetHeight } = ref.current;
setDimensions({
width: offsetWidth,
height: offsetHeight
});
}
}, []);
return (
<div>
<div ref={ref} style={{
width: '200px',
height: '100px',
backgroundColor: 'lightblue'
}}>
Measurable element
</div>
<p>Width: {dimensions.width}px</p>
<p>Height: {dimensions.height}px</p>
</div>
);
}
import { useLayoutEffect, useState } from 'react';
function FlickerPrevention() {
const [count, setCount] = useState(0);
const [color, setColor] = useState('red');
useLayoutEffect(() => {
// Change color before rendering, avoiding flickering
setColor(count % 2 === 0 ? 'red' : 'blue');
}, [count]);
return (
<div style={{ color }}>
<p>Colored text: {count}</p>
<button onClick={() => setCount(count + 1)}>
Increase
</button>
</div>
);
}
import { useLayoutEffect, useRef } from 'react';
function DOMSynchronization() {
const inputRef = useRef();
useLayoutEffect(() => {
// Focus element before rendering
if (inputRef.current) {
inputRef.current.focus();
}
}, []);
return (
<div>
<input
ref={inputRef}
placeholder="This input will get focus without flickering"
/>
</div>
);
}
useEffect and useLayoutEffect are powerful tools for working with side effects in React:
✅ When to use useEffect:
✅ When to use useLayoutEffect:
Key points:
Understanding the difference between these hooks allows you to optimize performance and improve user experience in React applications.
Want more articles for interview preparation? Subscribe to EasyAdvice, bookmark the site, and improve every day 💪