1. Omid Sayfun
  2. /
  3. Notebook
  • Home
  • About
  • Notebook
  • Token Usage
  • Whisper Usage
Tools
  • Agent Knowledge Base

React Component Lifecycle

November 28, 2024 · Updated on August 09, 2026

I was working on a React project that read scroll position and dynamic component height to render a scroll indicator. The measurements were wrong on the first paint, so I went back through the React component lifecycle to work out why.

Component lifecycle in React js has three phases:

  • Mount: the component is added to the DOM for the first time.
  • Update: props or state change, and React re-renders the component.
  • Unmount: the component is removed from the DOM.

React uses the virtual DOM to track changes and only touches the real DOM when it has to. The harder part is knowing when to run your own code inside each phase.

useLayoutEffect runs before the browser paints

useEffect covers most cases: tracking changes, and handling mount and unmount behavior. useLayoutEffect runs its logic after React has worked on the virtual DOM but before the browser paints anything on the screen. That makes it the right hook for layout calculations that must land before the user sees a frame.

In a class component these phases are named methods: componentDidMount, componentDidUpdate, and componentWillUnmount. In a functional component, both hooks cover all three phases, and the dependency array decides which phase you are in.

useEffect is asynchronous, useLayoutEffect is synchronous

The difference that matters is timing:

  • useEffect is asynchronous: it runs after the browser has painted the DOM. The user can see updates on the screen before the code inside useEffect executes. Use it for non-blocking work like API calls or logging.
  • useLayoutEffect is synchronous: it blocks the browser from painting until its code runs. Use it for measuring DOM elements or adjusting layout, because everything is in place before the user sees it.

So useLayoutEffect writes to the DOM before the first visible frame, and useEffect writes to it after. For a component that depends on precise measurements, that is the difference between a correct first paint and a visible jump.

Six hooks in one component, and the order they log in

This component registers both hooks three times each: with an empty dependency array, with no array, and with [count].

const Component = () => {
	const [count, setCount] = useState(0);
 
	useEffect(() => {
		console.log("UseEffect with empty dependency array");
	}, []);
 
	useLayoutEffect(() => {
		console.log("UseLayoutEffect with empty dependency array");
	}, []);
 
	useEffect(() => {
		console.log("UseEffect with no dependency");
	});
 
	useLayoutEffect(() => {
		console.log("UseLayoutEffect with no dependency");
	});
 
	useEffect(() => {
		console.log("UseEffect with count: " + count);
	}, [count]);
 
	useLayoutEffect(() => {
		console.log("UseLayoutEffect with count: " + count);
	}, [count]);
 
	console.log("React component rendered");
 
	return (
		<div>
			<button
				onClick={() => {
					console.log("Increase Button Clicked");
					setCount((c) => c + 1);
				}}
			>
				Increase
			</button>
			<div>count: {count}</div>
		</div>
	);
};
 

The widget below runs the same component, so you can compare the console output against what you expect.

Console Logs:

The render logs first, then the hooks fire in registration order

Here is the breakdown:

  1. Initial Render:
    • The component renders, logging "React component rendered".
    • Both useEffect and useLayoutEffect hooks with empty dependency arrays fire once, logging their respective messages.
  2. No Dependencies:
    • useEffect and useLayoutEffect with no dependency array fire every time the component renders (initial and updates).
  3. With [count] Dependency:
    • The useEffect and useLayoutEffect hooks with [count] fire only when count changes.
  4. Button Click:
    • Clicking the button triggers setCount, causing a state update. This:
      • Re-renders the component, logging "Increase Button Clicked" and "React component rendered".
      • Triggers the hooks as described above.

tl;dr

  • useEffect with an empty array runs once after the component mounts.
  • useEffect without a dependency array runs after every render.
  • useLayoutEffect behaves similarly but fires earlier in the update cycle.
  • Including dependencies (e.g., [count]) ensures the hook runs only when those values change.

Next time a measurement reads wrong on first paint, check which of the two hooks the code is using before you change the measurement.

Join My Newsletter

Occasional notes on software, tools, and things I learn. No spam.

Unsubscribe anytime.

Continue Reading

  • Embeddings rot too. Running pgvector in production.Aug 28, 2026
  • Stop shipping retrieval changes on vibesAug 25, 2026
  • Your RAG is confidently wrong without hybrid searchAug 21, 2026
  • Stop tuning everything. pgvector has three knobs that matter.Aug 18, 2026
  • Your agent's knowledge base is lying to you. Run these 14 checks.Aug 18, 2026