Timeline of a React Component With Hooks

Published Last updated v1.0.2

Understanding the order in which functional components run hooks can be helpful in writing React correctly and effectively. I made this diagram that shows just that. Look at it and click through it first, there's a description and a quiz afterward. Let's dive in!

function Component() {
const [inputValue, setInputValue] = useState(() => getInitialInput());

const ref = useRef();

useEffect(() => {
...

return () => {
...
};
}, [inputValue]);

useLayoutEffect(() => {
...

return () => {
...
};
});

const id = "textInput";

const heavy = useMemo(() => {
return doSomethingHeavy(inputValue)
}, [inputValue])

return (
<div>
<label htmlFor={id}>Text</label>
<input
id={id}
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
placeholder="Type something to update"
ref={ref}
autoFocus
onFocus={(e) => {
console.log("onFocus");
}}
/>
<Child heavy={heavy} />
</div>
);
}

“Rendering” is React calling your components to figure out what to display on screen. React’s rendering process must always be pure, components should only return their JSX, and not change any objects or variables that existed before rendering.
In render, all local component variables are evaluated and effects are scheduled.

On the initial render, useState, useReducer, useMemo, useRef and useCallback are initialized.

  • For useState, useReducer, and useRef, React saves the initial value once and ignores it on all subsequent renders.
  • The value returned by useMemo is the result of calling the function passed to it.
  • React returns (not call!) the function passed to useCallback during the initial render

A React component is ’just’ a function that returns markup. You dont call the component yourself, you give it to React in a tree-shaped chain of components and React will call it for you when it needs to update the UI. React Hooks augment a component function, allowing you to hook into React to give it special abilities. Hooks run in a certain order. The React docs don't recommend you thinking in ‘timelines’ for components and urge you to think more in terms of ”data” and ”synchronization” than ”events”. But timing still matters, having an incomplete idea of the flow may trip you up and can lead you to write buggy code, so it’s good to ‘know the flow’.

Previously [with class components], you were thinking from the component’s perspective. When you looked from the component’s perspective, it was tempting to think of Effects as “callbacks” or “lifecycle events” that fire at a specific time like “after a render” or “before unmount”. This way of thinking gets complicated very fast, so it’s best to avoid it.

Lifecycle of Reactive Effects — Thinking from the Effect’s perspective

Test your knowledge with these React Riddles! You should be able to get the answer by clicking through the diagram. Please try to answer questions yourself before revealing the answer.

Q: Will the <Child /> component also rerender in an update of <Parent />? Does the type of update (pro-active or passive) trigger of <Parent /> matter in this case?

Q: Is it safe to perform side-effects in a callback ref?

Q: How many times will a callback ref be called in an update?

Q: How many times will a stable callback ref be called in an update?

Q: What will happen if you pass a state setter to a ref attribute?: <input ref={setSomeState} />

Q: Can you read a DOM ref in render?

Q: Is it safe to read a DOM ref in a useLayoutEffect or useEffect setup function?

Q: Is it safe to read a DOM ref in a useEffect or useLayoutEffect cleanup function?

Q: Why is initializing state in useEffect inefficient?

Q: Just by looking at this code block, determine in what order will the console.log's appear in the console when <Parent/> is rendered.

Bonus question: will ref.current in the onFocus be true or false?


_44
function Child() {
_44
console.log("Called Child");
_44
_44
useEffect(() => {
_44
console.log("Child useEffect");
_44
}, []);
_44
_44
useLayoutEffect(() => {
_44
console.log("Child useLayoutEffect");
_44
}, []);
_44
_44
return <div>Child</div>;
_44
}
_44
_44
function Parent() {
_44
console.log("Called Parent");
_44
_44
const ref = useRef(false);
_44
_44
useEffect(() => {
_44
console.log("Parent useEffect");
_44
}, []);
_44
_44
useLayoutEffect(() => {
_44
ref.current = true;
_44
console.log("Parent useLayoutEffect");
_44
}, []);
_44
_44
return (
_44
<main>
_44
<button
_44
autoFocus
_44
onFocus={() => {
_44
console.log("Focusing button, ref.current=", ref.current )
_44
}}
_44
>
_44
Button
_44
</button>
_44
{console.log("Before <Child/>")}
_44
<Child />
_44
{console.log("After <Child/>")}
_44
</main>
_44
);
_44
}

  • Setting state in a components render will make React throw away the returned JSX and immediately retry rendering 8 9. In this diagram, it would go from "Return React elements" right back to "Start render". This is more efficient than setting state in a useEffect, which would go through the usual flow fully again.
  • I've purposefully left out useInsertionEffect because you’ll very likely never need it, unless you’re writing a css-in-js library. But in case you‘re wondering, it runs before "Insert/Update DOM elements" and it has no cleanup 10.
  • useEffect fires before paint when state is set in a useLayoutEffect. I'll just refer you to useEffect sometimes fires before paint
  • Starting in React 18, the function passed to useEffect will fire synchronously before layout and paint when it’s the result of a discrete user input such as a click, or when it’s the result of an update wrapped in flushSync. 11
  • Concurrent features, just because I haven’t looked in to that.
  • React Context; a component consuming a context will update when the context value is changed.
  • Maybe more things I‘m not aware of?

Thanks to all the people who wrote something that was included in here.

Special thanks to Mark Erikson for his feedback and suggestion to include a quiz!

Diego Haz for this quiz tweet on timing of autoFocus & onFocus and Jamon Holmgren for another quiz tweet.

I couldn‘t include footnotes in the text in the diagram component so instead they're piled up here ¯_(ツ)_/¯.

Render
12
Ref
13
2
14
5
Callback refs
15
3
Browserpaint
16
Browser paint
17
useEffect
18
11
19
20
useEffect cleanup
21
22
23
useLayoutEffect
24
Update phases
25
Set state in render
8
26
State initializer function
27
28
useCallback
29
useMemo
29

  1. Unlike componentDidMount and componentDidUpdate, the function passed to useEffect fires after layout and paint, during a deferred event. This makes it suitable for the many common side effects, like setting up subscriptions and event handlers, because most types of work shouldn’t block the browser from updating the screen. React Docs useEffect - Timing of effects

  2. Another important principle specifies the order in which refs are set and unset. The part we rely on the most is that the ref is always set before useLayoutEffect [...] for the corresponding DOM update is called. This, in turn, means that useEffect and parent useLayoutEffect are also called after the ref is set. In a single render, all the ref unsets happen before any set — otherwise, you’d get a chance to unset a ref that’s already been set during this render. Next, useLayoutEffect cleanup during re-rendering runs right between ref unset and set, meaning that ref.current is always null there. To be honest, I’m not sure why it works this way, as it’s a prime way to shoot yourself in the foot, but this seems to be the case for all React versions with hooks. Thoughtspile: So you think you know everything about React refs

    2 3 4 5
  3. If the ref callback is defined as an inline function, it will get called twice during updates, first with null and then again with the DOM element. This is because a new instance of the function is created with each render, so React needs to clear the old ref and set up the new one. You can avoid this by defining the ref callback as a bound method on the class, but note that it shouldn’t matter in most cases. React docs: Refs and the DOM — Caveats with callback refs

    2
  4. React guarantees that setState function identity is stable and won’t change on re-renders. This is why it’s safe to omit from the useEffect or useCallback dependency array. React Docs Hooks Reference: useState

  5. In React, every update is split in two phases: During render, React calls your components to figure out what should be on the screen. During commit, React applies changes to the DOM. In general, you don’t want to access refs during rendering. That goes for refs holding DOM nodes as well. During the first render, the DOM nodes have not yet been created, so ref.current will be null. And during the rendering of updates, the DOM nodes haven’t been updated yet. So it’s too early to read them. React sets ref.current during the commit. Before updating the DOM, React sets the affected ref.current values to null. After updating the DOM, React immediately sets them to the corresponding DOM nodes. Usually, you will access refs from event handlers. If you want to do something with a ref, but there is no particular event to do it in, you might need an Effect. We will discuss effects on the next pages. Beta React Docs: Manipulating the DOM with Refs – When React attaches the refs

    2
  6. The ref value ref.current will likely have changed by the time this effect cleanup function runs. If this ref points to a node rendered by React, copy ref.current to a variable inside the effect, and use that variable in the cleanup function. eslint-plugin-react-hooks Warning on reading ref.current in effect cleanup

  7. What will be logged on the console when this React component loads?
    [code]
    With [client side rendering], the correct answer is false. Layout effects run after DOM mutations. This happens after the browser has rendered and autofocused the element. The onFocus handler will be called but before the [useLayoutEffect] callback. But here's a curious thing: the new useInsertionEffect hook runs before DOM mutations and before the autoFocus event. If you replaced useLayoutEffect with useInsertionEffect, the answer would be true (with CSR).
    Tweet by Diego Haz

  8. When you update a component during rendering, React throws away the returned JSX and immediately retries rendering. To avoid very slow cascading retries, React only lets you update the same component’s state during a render. If you update another component’s state during a render, you’ll see an error. A condition like items !== prevItems is necessary to avoid loops. You may adjust state like this, but any other side effects (like changing the DOM or setting a timeout) should remain in event handlers or Effects to keep your components predictable. Although this pattern is more efficient than [resetting state in] an Effect, most components shouldn’t need it either. React Beta Docs: You Might Not Need an Effect — Adjusting some state when a prop changes

    2
  9. In the rare case that none of these apply, there is a pattern you can use to update state based on the values that have been rendered so far, by calling a set function while your component is rendering. [..] Note that if you call a set function while rendering, it must be inside a condition like prevCount !== count, and there must be a call like setPrevCount(count) inside of the condition. Otherwise, your component would re-render in a loop until it crashes. Also, you can only update the state of the currently rendering component like this. Calling the set function of another component during rendering is an error. Finally, your set call should still update state without mutation — this special case doesn’t mean you can break other rules of pure functions. React Docs useState - Storing information from previous renders

  10. The signature [of useInsertionEffect] is identical to useEffect, but it fires synchronously before all DOM mutations. Use this to inject styles into the DOM before reading layout in useLayoutEffect. Since this hook is limited in scope, this hook does not have access to refs and cannot schedule updates. Note: useInsertionEffect should be limited to css-in-js library authors. Prefer useEffect or useLayoutEffect instead. React Hooks Reference: useInsertionEffect

  11. [...] not all effects can be deferred. For example, a DOM mutation that is visible to the user must fire synchronously before the next paint so that the user does not perceive a visual inconsistency. (The distinction is conceptually similar to passive versus active event listeners.) For these types of effects, React provides one additional Hook called useLayoutEffect. It has the same signature as useEffect, and only differs in when it is fired. Additionally, starting in React 18, the function passed to useEffect will fire synchronously before layout and paint when it’s the result of a discrete user input such as a click, or when it’s the result of an update wrapped in flushSync. This behavior allows the result of the effect to be observed by the event system, or by the caller of flushSync. Timing of effects

    2
  12. React components let you write rendering code without worrying too much about time. You describe how the UI should look at any given moment, and React makes it happen. Take advantage of that model!

    Don’t try to introduce unnecessary timing assumptions into your component behavior. Your component should be ready to re-render at any time.
    Writing Resilient Components: Principle 2: Always Be Ready to Render — Overreacted

  13. To get the basics out of the way, ref is set to the DOM node when it’s mounted, and set to null before removing the DOM node. No surprises this far. One thing to note here is that a ref is, strictly speaking, never updated. If a DOM node is replaced by some other node (say, its DOM tag or key changes), the ref is unset, and then set to a new node. (You may think I’m being picky here, but it’s goint to prove useful in a minute.) [...] The part I was not aware of is that the identity of ref prop also forces it to update. When a ref prop is added, it’s set to DOM node. When a ref prop is removed, the old ref is set to null. Here, again, the ref is unset, than set again. This means that if you pass an inline arrow as a ref, it’ll go through unset / set cycle on every render (sandbox): So, why does it work that way? In short, it allows you to attach refs conditionally and even swap them between components [...]. So far we’ve learnt that refs are set node when the DOM mounts or when the ref prop is added, and unset when the DOM unmounts or the ref prop is removed. As far as I’m concerned, nothing else causes a ref to update. A changing ref always goes through null. Thoughtspile: So you think you know everything about React refs

  14. Every React component goes through the same lifecycle: 1) A component mounts when it’s added to the screen. 2) A component updates when it receives new props or state. This usually happens in response to an interaction. 3) A component unmounts when it’s removed from the screen. It’s a good way to think about components, but not about Effects. Instead, try to think about each Effect independently from your component’s lifecycle. An Effect describes how to synchronize an external system to the current props and state. As your code changes, this synchronization will need to happen more or less often. [...] Intuitively, you might think that React would start synchronizing when your component mounts and stop synchronizing when your component unmounts. However, this is not the end of the story! Sometimes, it may also be necessary to start and stop synchronizing multiple times while the component remains mounted. React Docs: Lifecycle of Reactive Effects — The lifecycle of an Effect

  15. React also supports another way to set refs called “callback refs”, which gives more fine-grain control over when refs are set and unset. Instead of passing a ref [object] created by [useRef], you pass a function. The function receives the [...] HTML DOM element as its argument, which can be stored and accessed elsewhere. React docs: Refs and the DOM — Callback Refs

  16. There’s a subtle difference between having an [inline functions (a callback)] as your ref prop and a ref object or a stable callback — the [inline function] has a new identity on every render, forcing the ref to go through an update cycle [where it will be] null. This is normally not too bad, but good to know. Thoughtspile.tech: So you think you know everything about React refs

  17. After rendering is done and React updated the DOM, the browser will repaint the screen. Although this process is known as “browser rendering”, we’ll refer to it as “painting” to avoid confusion in the rest of these docs. React docs: Render and Commit — Epilogue: Browser paint

  18. Does useEffect run after every render? Yes! By default, it runs both after the first render and after every update. [...] Instead of thinking in terms of “mounting” and “updating”, you might find it easier to think that effects happen “after render”. React guarantees the DOM has been updated by the time it runs the effects. React docs: Using the Effect Hook

  19. Every time we re-render, we schedule a different effect, replacing the previous one. In a way, this makes the effects behave more like a part of the render result — each effect “belongs” to a particular render. Effects Without Cleanup — Detailed Explanation

  20. Unlike componentDidMount and componentDidUpdate, the function passed to useEffect fires after layout and paint, during a deferred event. This makes it suitable for the many common side effects, like setting up subscriptions and event handlers, because most types of work shouldn’t block the browser from updating the screen. React Docs useEffect - Timing of effects

  21. If you’re used to classes, you might be wondering why the effect cleanup phase happens after every re-render, and not just once during unmounting. There is no special code for handling updates because useEffect handles them by default. It cleans up the previous effects before applying the next effects. [...] This behavior ensures consistency by default and prevents bugs that are common in class components due to missing update logic. Explanation: Why Effects Run on Each Update

  22. Often, effects create resources that need to be cleaned up before the component leaves the screen, such as a subscription or timer ID. To do this, the function passed to useEffect may return a clean-up function. For example, to create a subscription: Hooks API Reference - useEffect Cleaning up an effect

  23. Effects are reactive blocks of code. They re-synchronize when the values you read inside of them change. Unlike event handlers, which only run once per interaction, Effects run whenever synchronization is necessary.
    You can’t “choose” your dependencies. Your dependencies must include every reactive value you read in the Effect. The linter enforces this. Sometimes this may lead to problems like infinite loops and to your Effect re-synchronizing too often. Don’t fix these problems by suppressing the linter!
    React Beta Docs: Lifecycle of Reactive Effects - What to do when you don’t want to re-synchronize

  24. useEffect should run after paint to prevent blocking the update. But did you know it’s not really guaranteed to fire after paint? Updating state in useLayoutEffect makes every useEffect from the same render run before paint, effectively turning them into layout effects. Confusing? Let me explain. [...] There is, however, a more interesting passage in the docs: ”Although useEffect is deferred until after the browser has painted, it’s guaranteed to fire before any new renders. React will always flush a previous render’s effects before starting a new update.” This is a good guarantee — you can be sure no updates are missed. But it also implies that sometimes the effect fires before paint. If a) effects are flushed before a new update starts, and b) an update can start before paint, e.g. when triggered from useLayoutEffect, then the effect must be flushed before that update, which is before paint. useEffect sometimes fires before paint

  25. Conceptually, React does work in two phases:1) The render phase determines what changes need to be made to e.g. the DOM. During this phase, React calls [components] and then compares the result to the previous render. 2) The commit phase is when React applies any changes. (In the case of React DOM, this is when React inserts, updates, and removes DOM nodes.) Introducing the React Profiler — Browsing commits

  26. How do I implement getDerivedStateFromProps?
    While you probably don’t need it, in rare cases that you do [...], you can update the state right during rendering. React will re-run the component with updated state immediately after exiting the first render so it wouldn’t be expensive. Here, we store the previous value of the row prop in a state variable so that we can compare.
    Hooks FAQ – How do I implement getDerivedStateFromProps?

  27. React saves the initial state once and ignores it on the next renders. Although the result of [the function call passed to useState] is only used for the initial render, you’re still calling this function on every render. This can be wasteful if it’s creating large arrays or performing expensive calculations. To solve this, you may pass it as an initializer function to useState instead. [...] If you pass a function to useState, React will only call it during initialization. useState: Avoiding recreating the initial state

  28. React saves the initial state once and ignores it on the next renders. Although the result of [a function call passed to the initial value of useReducer] is only used for the initial render, you’re still calling this function on every render. This can be wasteful if it’s creating large arrays or performing expensive calculations. To solve this, you may pass it as an initializer function to useReducer as the third argument instead. This way, the initial state does not get re-created after initialization. useReducer: Avoiding recreating the initial state

  29. fn: [parameter] The function value that you want to memoize. It can take any arguments and return any values. React will return (not call!) your function back to you during the initial render. On subsequent renders, React will return the same function again if the dependencies have not changed since the last render. Otherwise, it will give you the function that you have passed during the current render, and store it in case it can be reused later. React will not call the function. The function is returned to you so you can decide when and whether to call it. React Docs: useCallback parameters

    2