Kent C. Dodds: 0:00 Before we move on from useEffect, I wanted to show you this hooks flow diagram prepared by my friend, Donovan. You can find it on GitHub. It's a great representation of the flow of what React is doing over time for your React components. [0:17] Hopefully, this can help you visualize the way that your React components mount, update, and unmount and the order in which things are run. Then, I'm going to show you the example here that I have for our little demo that you can absolutely play around with.
0:31 The way that hooks work is we start out with our lazy initializers. That's the function API for useState where you pass in a function. That's what we call a lazy initializer.
0:43 When our component is mounting to the page, when it's first being rendered, it's never been on the pitch before, the first thing that React as going to do is it's going to run those lazy initializers. Then, it's going to run the rest of our render function. That's all of the contents of our function where that useState resides.
1:00 Then, it's going to do updating of the DOM. We say, "Hey, React. I want you to put a div right here." React is going to say, "OK, cool. Let me make that div." That's what React updates the DOM is all about.
1:12 At that point, it's going to run layoutEffects, which you'll learn about in the future. This is basically like a useEffect callback. It's very similar just slight differences. We can learn about that later.
1:25 After it runs the layoutEffects, React is going to stop running and say, "Hey, browser. By the way, I updated the DOM. Why don't you go ahead and paint the screen."
1:36 What that means is the browser says, "Oh, cool. I got some DOM updates. Let me take those DOM updates and show the user what those DOM updates were."
1:45 There's a period of time between when you add a class name to an element and when the user sees that change reflected in what is being shown to them. That process is called painting.
2:00 That's what happens as soon as React updates the DOM and then runs your layoutEffects. It will allow the browser to paint the screen. Then, it runs your effects. Updating local storage or whatever it is that you're doing.
2:13 Then, we just hang out and wait for the user to do some interaction or some subscription to update or whatever. Ultimately, we get some state update and that triggers an update.
2:24 Then, we go through this process. We don't run those lazy initializers this time, and instead, just run the contents of the component function. That's the render phase.
2:34 React will update the DOM again, saying, "Hey, DOM. We need to update this class name or add this div or whatever." Then, React will run what's called the cleanup phase for our layoutEffects. We'll look at that in our example here in just a second.
2:48 Once it runs those cleanups, it will run the layoutEffects. It will yield to the browser to say, "Hey, browser. I made some updates to the DOM, why don't you show those to the user?" The browser is like, "Cool. Thanks, React."
3:00 Once the browser is done updating the screen, React will say, "Hey. I'm going to go cleanup any side effects that we had in the last render. Then, I'm going to run all the new effects that need to be run in this render."
3:14 Ultimately, the user navigates away from the page, or we click on a checkbox and something goes away. Somehow, a component gets removed from the screen. All that happens in that phase, it's called unmounting. All that is going to happen is a cleanup of our layoutEffects and then a cleanup of our effects.
3:33 With all of this as the backdrop for this, we have this example, hook-flow.js. You can find it in the examples directory. You can pull this up right here, that URL.
3:44 We've got two components in here. One is the child. I have a bunch of console.logs in here with different color coding so that you can follow along what's going on in our app. We've got a console.log in the render. We've got a console.log in a lazy initializer. We've got one in a useEffect.
4:03 Then, the useEffect is returning a function. That is our cleanup function. If you return a function from useEffect, React will call that when it's time to cleanup that useEffect.
4:12 Then, we have three different versions of useEffect. Here's one with no dependencies. Here's one with an array of empty dependencies. Also, no dependencies, but we're explicitly saying we do not depend on anything. Whereas with no dependencies listed even, we're saying we depend on everything. Then, this useEffect here has a single dependency. We'll be able to compare how those different things are running.
4:36 Then, we're going to create our element. We'll log that we've finished rendering, and then we'll return that element. The app is, basically, the exact same thing. It just manages a little different state here. Where our child manages a state for the count, the app here is managing state for whether or not it should show the child.
4:54 We're still getting the console.log for render start and for our useState update, for three useEffects, each with a different variation on dependencies, each with a cleanup. Then, we create our element for the UI. We log that we've finished, and then we return that element.
5:13 Right from the get-go, once we get on to the page, we're going to start out with this mount phase for our app. Right here, we're going to see we have App: render start. That's the first thing that happens. Then, we get a useState(() => false).
5:29 This is our lazy initializer right here. We can get this log. We call React useState. Immediately, React says, "Oh, OK. Cool. You gave me a function. Let me just call that right away so that I know what to give you in my return value for this showChild value." It does call that immediately.
5:47 Then, we call all these useEffects. You'll notice that the console logs inside of those are not actually being called until after we call all of them, after we create the elements, and then we log that we finished rendering.
6:02 Once our render is all done, we finished our rendering, React updates the DOM. It runs the layout effects. Then, the browser paints the screen. Then, it runs our effects. That's what we're getting right here, these three effects. All three of them are going to run. That's cool.
6:19 Let's go ahead and trigger an update so we see what happens in this case. I'm going to highlight this one so we remember that's where we're at. Then, I'm going to click show child. That will trigger a re-render because that's a state update. We get App:render start.
6:34 You remember that's the first thing that happens here. Then, we get App:render end. We no longer are getting our lazy initializer here with this useState. That's not going to happen anymore because it's a lazy initializer. It's only for the initialization of our app component.
6:49 Something that is interesting, it may take you by surprise, is that we get this render end, but this time we're actually rendering the Child. Shouldn't the Child get called first? We have our render for the Child. That should happen before the render of the app finishes.
7:08 This is a really important point. Just because you're creating a React element of a specific type, doesn't mean that React is actually going to call into that function. When you do this, remember you're calling React createElement(Child). That's what that line is doing.
7:25 You're not calling the function. You're not saying Child invoked. No. That's not what's going on. You're just creating an element that is of type Child. React is the one that's responsible for calling it. React is not calling it until it's actually time to do the rendering.
7:41 That's an important point to keep in mind. You could do , do a million of these, and if those are never actually rendered, then you're never going to see that Child getting called.
7:52 We are in fact rendering this Child. It does ultimately need to be rendered to the page. When React gets to that point, it comes up here. We get a call of the Child:render start. There it is right there. Then, we have our lazy initializer for useState. That's what's going on right here. We call this useState.
8:11 Because this is a mount, we're going to call our lazy initializer immediately. We can take a look at that. What we experienced with the app right here is an update. Then, what we're experiencing with the Child is a mount. Keep those differences in mind.
8:25 That's why we're getting this lazy initializer. Then, we call all these useEffects. We create our element. Then, we finish rendering right here. Then, React says, "Cool. We've mounted this thing. What's the next step?"
8:37 We need to let the browser paint the screen. Then, we'll run all of the effects. That's what happens here next, is it takes all of these effects and runs those effect callbacks. Once the Child is all done, running all three of those effects callbacks for the mount, then the app parent can run it through its next phase.
8:58 Let's take a look at that hook flow. Remember the app is here. We ran the render for this component. Then, we started the mount for the Child. Now, we're continuing here with updating the DOM where we need to clean the layout effects, run the layout effects. We don't have any of those. Remember, that's for future a lesson.
9:18 Then, the browser painted the screen. Now, we're going to run the cleanup of the effects. That's why we're seeing this useEffect with no dependencies getting cleaned up. It's going to rerun those effects on every single re-render of this component. Every update is going to rerun those effect callbacks.
9:34 If the effect callback needs to be run, then it needs to run the clean up first. That's why we're getting both of these where this one has no dependencies. We're basically saying this needs to rerun on every render, whereas this one has a single dependency. That dependency changed so that also needs to be cleaned up.
9:52 Once both of those cleanups run, then we can run the useEffect callback and synchronize the state of the world with the state of our app. In our case, that is simply logging to the console.
10:02 Let's go ahead and do an update on the child. Here we have a button. When we click the button, we're going to get the setCount to update the count based on the previous value for the count. That should trigger a re-render of the child.
10:15 Let's take a look at that. I'm just going to highlight this really quick, so we can keep track of that. I'll click this once. Boom, we get a re-render.
10:22 One thing that you'll notice right here, is that the child re-rendered but the app did not. The app does not care at all about what is happening in the child. The app is totally isolated from the render updates of the child.
10:37 Because the state update happened within the child, that's where our re-render is going to happen. We start with the render start. Remember, no lazy initializer here, because this is an update. According to our hooks' chart, we don't get lazy initializers run on updates.
10:52 Then, we call all of these useEffects. Those do not get logged immediately. Instead, we log that the child: render end happened.
11:01 Then, we call the cleanups for our useEffects. Just like we did with our app here, we're now saying, "Hey, this useEffect runs on every single render, so I need to clean that up," and "Hey, this useEffect runs whenever the count changed, it did, so I need to clean that up."
11:16 Once those cleanups happen, then we can run the useEffect callbacks again, to synchronize the state of the world with the state of our app.
11:23 All right. Finally, let's take a look at this unmount cycle right here. Presumably, all that should happen is cleanup LayoutEffects, which we don't have, and cleanup Effects, which we do.
11:34 Let's take a look at what happens when I uncheck the show child, that showChild will no longer be rendered to the page, instead, we'll just render .
11:41 Let's take a look at what happens. Let me highlight this, and then uncheck show child. Boom, there we go. What happened here is there was a state update inside of the app, and so that's why the app itself gets re-rendered.
11:56 We start the render and we finish the render. Then, the child is like, "Whoa, whoa. I'm leaving. I'm out. I'm getting unmounted from the page."
12:03 We've got to run the cleanup for everything. Even though the count didn't change, even though this doesn't have any dependencies, every one of these is going to have the cleanup phase run, because we are unmounting.
12:17 Then, because there was a state change and we have some useEffects that rely on that state, we're going to run those useEffects cleanup, for the useEffect that depends on everything, and the useEffect that depends on the thing that changed. Then, we'll run the effect callbacks for both of those.
12:33 If this was a little bit fast for you, or it was a little confusing or something, I strongly recommend that you come in here and you play around with things, you add some additional console.logs or whatever you want to do to explore this.
12:45 It's not totally necessary for you to understand the flow of all of your hooks, but it does help sometimes to understand what order things are run in.
12:55 Definitely spend a little bit of time playing around with this example. You can use this as a reference anytime you are a little bit confused about the order in which these hooks are going to be run.