Understanding the order in which React hooks are called can be really helpful in using React hooks effectively. This chart can be really helpful in understanding this, and in this lesson we’ll explore the lifecycle of a function component with hooks with colorful console log statements so we know when one phase starts and when it ends.
I recommend you watch this one slowly and watch it as many times as you need to. Also definitely play around with the code yourself until you feel relatively comfortable with this. Understanding all of this is not critical to your success with using React, and most of the time you won’t need to think about this at all, but understanding it can help you at times.
Kent C. Dodds: [0:00] Sometimes it could be useful to understand the order in which your code is going to be run when you're using React Hooks. I've made this little app that has a checkbox for showing a child, and inside of this box this will be rendered when that checkbox is checked. When you click on this button, it's going to increment the count.
[0:17] The way that this works is we have a child component here that's maintaining a countState, and then that has a whole bunch of useEffects here, which are simply logging to the console when the callback is called and logging to the console in a cleanup function that it provides.
[0:32] Then we create our React element for rendering to the page, and then we log to the console that our render is finished and then we return that React element we created.
[0:42] We do the same thing for our App component, except here we're maintaining a showChild Boolean state. We have all those useEffects, and then we render a few React elements here for rendering this UI.
[0:54] Let's go ahead and take a look at what happens when we initially load this page. I'm going to refresh, I'll open up our DevTools, and I'll scroll down here to the app, so we can follow along in the code with what we're seeing in the console.
[1:08] The first thing that we see is this app render start. That's the first thing that happens when we call ReactDom.render our app. It calls our app Function Component. The next thing that happens is we call react.useState and immediately React is going to call this function to retrieve the initial state for our show child state. That's why we're getting this app useState callback called.
[1:31] Then, we call all these React useEffects, but you'll notice that the logs in those are not the next thing that appear in our console. Instead, we actually create this element and then we get a log to the console for app render end. Once that happens, React actually is updating the DOM. Then, asynchronously later, it's going to call our useEffect callbacks, one at a time in the order in which they were called.
[1:55] We come up here to the top, mostly, app useEffect to no deps is the first one that appears. We don't get our cleanup because there's no cleanup necessary yet, because right now, we're just mounting the component, and we haven't had any updates yet.
[2:08] Next, we get our useEffects empty deps, right here. We have an empty list of dependencies there and then useEffect with dep. That's our show child's state. That's the next thing that gets called here.
[2:21] Now, let's take a look at what happens when we click on show child. Remember that this is the last console log that we saw when we initially mounted the component. If I click show child, then we're going to get an app render start. When we click show child, that triggers this on change to set show child to the checked value of our check box input.
[2:44] This set show child is going to trigger a re-render of our app, which is why we get our app render start. Come up here to the top again. We'll say app render start, and we'll go through all of this code just like we had at the previous render, except this time you'll notice we don't have an app use state callback, we go straight from app render start to app render end.
[3:06] This is because React has already retrieved the initial state value for our show child state, and it doesn't need to retrieve that value again. Any time you use a function call back for use state, that function is only going to be called when this component is initially rendered for the rest of the lifetime of that component.
[3:24] We go through all these useEffect cause again, we create our element, and then we lock to the console that the app render has finished. Then React calls our child to start rendering of that child. One thing that I want to stress here is that we're creating our element which includes creating the child right here.
[3:42] You'll notice that we get to this line of code before we start rendering the child. The important thing to remember here is that just because you create a React element, doesn't mean that React element's function is going to get called, because you're not calling the function, React is.
[3:58] React will only call that function when that component is actually going to be rendered. That's really important because you could say Const UI I will not render, and render a whole bunch of these all day long, and unless those things are added to some UI that's actually going to get rendered, all that you're creating is a bunch of React element objects, and you won't actually be calling into that child function.
[4:25] Let's go ahead and scroll up to this child render. Start here, and then we get our use state call back, because this is the first time that this use state is going to be called, so it needs to retrieve the initial value of zero.
[4:38] Then we call all of these useEffects, just like in an app, we create an element, and then we get a log for this render end. Then after the entire DOM has been updated, React is going to start calling our useEffects. It calls then in the order in which they are called, but starting at the child component.
[4:56] We get child useEffect no deps is called, we get the child useEffect empty deps is called, and then we get the child effect with dep is called, and the dep here is our count value. Then we're going to start calling the app useEffect callbacks.
[5:14] You'll notice that we actually call the cleanup first, and then we call the setup. Here we have the cleanup and then the setup. We also do the same for the cleanup when we have a dependency, and then the setup when we have a dependency. You'll notice that the cleanup for both of these is called before the setup for both of these, and the cleanups are both called in the order in which the appear, just like the setups.
[5:41] You'll also notice that this useEffect callback and its cleanup were not called. That's because useEffect callbacks are only called if they have no dependency listed or if they have a dependency listed, and one of those dependencies is changed.
[5:55] We have a dependency list, but it's empty, and so therefore, none of the dependency is changed, because it doesn't have any dependencies. This useEffect callback and its cleanup will not be called on updates.
[6:07] Let's go ahead and click on this button. We'll remember that this is the last log that we saw for that last update. When I click on that button, we're going to call setCount, and we're providing a function updater function where we take the previous value of the state and return the new value of our state.
[6:24] Here, we're just going to take the previous count and add 1 to it. This is going to trigger a re-render. Let's take a look at what happens here. I click on that. You'll notice that the app has none of its logs called. That's because the state update actually only resides within this component, so React knows that this component is the only one that needs to be re-rendered.
[6:45] Let's follow the console.logs. First we start with this child render start. We call this again, and we don't get a log for our useState callback, because this component's already been rendered, and we already retrieved the initial value and we no longer need that initial value, so React doesn't bother calling this function anymore.
[7:01] Then we call all these useEffect hooks. We create our React element for our UI, and then we call this child render end. Then our useEffect cleanups are called in order if those particular useEffects need to be rerun. In this case, we haven't listed any dependencies, so this will be rerun on every render of this component, so we'll get the cleanup there.
[7:25] This one is listing a dependency array, and there are no dependencies that can change, so it's not going to be called. This one does have a dependency array, and that dependency did change, so the cleanup is called here.
[7:39] Then we start with the setups for the useEffect that has no dependencies and the useEffect that has a dependency that changed. This is the last one that was called. If we click this again, we'll see the exact same order of calls, and then if we uncheck show child...Let's go ahead and highlight that, so we know which one was the last that we called, and then we uncheck show child.
[8:04] That's going to trigger a re-render of our app component, because we changed that showChildState using setShowChild. That triggers a render start here. We go through all of this code, again skipping the useState callback.
[8:21] We come down here, create our elements, and then render end right here. Then we're doing a cleanup on all of the children, because the child is being removed from the page, so we're now rendering null.
[8:34] React notices the previous JSX that you gave me included the child, and this next JSX that you gave me does not include the child. That means I need to remove the child from the page, and so I'm going to unmount the component and call all of the cleanups for all of the useEffects that that child had going.
[8:50] Let's come up here, and we'll see that that happens in the order in which they appear in the code. We see our no deps cleanup runs. We'll see our empty deps cleanup run. Even though we have listed dependencies but none of them changed because we have no dependencies, our cleanup is going to run, because we're unmounting this component, and then we get a cleanup of the effect with dependencies.
[9:13] Even though this count value didn't change, it's going to be called, because this component is getting unmounted.
[9:18] Then because our state changed, in the app, we're going to run some cleanups for the useEffects that are relevant. Here, we have this cleanup is getting called, because it has no dependencies. This cleanup is not getting called, because it has an empty dependency array. Then this cleanup is getting called, because the dependency that it has has changed.
[9:40] Then we go ahead and run the effects that are relevant, the useEffect with no dependencies and the useEffect with a dependency that changed.
[9:50] I encourage you to play around with this code example. Also, something else that might help you is this diagram from Donovan. You go to donovan⁄hookflow on GitHub and scroll down here and you'll see the diagram that Donovan has created.
[10:05] We get our lazy initializers. That's our useState callback. We get our render function that finishes after the lazy initializers are all called. React will update the DOM.
[10:16] React also has a useLayoutEffect hook that will be run here. That operates pretty similarly to the useEffect. The browser updates the screen based on the updates to the DOM that React made and then our effects are going to be called. This is exactly what we observed in our example.
[10:33] When there's a state update, we get our render called, our lazy initializers are not called. React is going to update the DOM, we get a cleanup of layout effects and then our layout effects are run. Browser updates the screen and then we get a cleanup of our effects and then our effects are run. When the component is unmounted, we get the cleanup of all of our effects.
[10:54] Having a firm understanding on the order in which these things are called is not totally necessary for you to be effective with React. It can help you in some situations, so I encourage you to play around with this.