Refactor App.js
We get our first real taste of adding hooks while refactoring the App component.
Get the project source code below, and follow along with the lesson material.
Download Project Source CodeTo set up the project on your local machine, please follow the directions provided in the README.md
file. If you run into any issues with running the project source code, then feel free to reach out to the author in the course's Discord channel.
This lesson preview is part of the The newline Guide to Modernizing an Enterprise React App course and can be unlocked immediately with a \newline Pro subscription or a single-time purchase. Already have access to this course? Log in here.
Get unlimited access to The newline Guide to Modernizing an Enterprise React App, plus 70+ \newline books, guides and courses with the \newline Pro subscription.

[00:00 - 00:28] The app component is where our whole React app begins, and while it is a fairly straightforward component to change from a class to a function, there is the little matter of the navbar components checkout count prop, which displays the number of items currently in the checkout. This state should get updated every time a user adds or removes an item from the checkout, so we'll have to account for this functionality during our refactor to make sure that it stays the same.
[00:29 - 00:42] In this lesson, we'll learn to sub in useState and use_effect hooks to recreate the functionality of class components. To get started, let's change app from a class to a function.
[00:43 - 01:11] In your IDE, go ahead and open up the app.js file in your client/source_cont ainers_app folder, and let's start by replacing the class constructor on line 13 with a function declaration. And as it's now a functional component, we can remove the render, the object destructuring, before it's passed to the JSX, and the additional curly braces at the end of the return statement.
[01:12 - 01:21] Thanks to functional components, that return is all we need now to render the components JSX. So let's delete the following code right down here.
[01:22 - 01:29] It's 50 through 53. Delete that and delete this now unused curly brace as well.
[01:30 - 01:44] Easy enough so far. The next thing that we're going to tackle following our refactoring recipe that we talked about in the last lesson is replacing our class's constructor and state with functional useState declarations instead.
[01:45 - 01:59] So let's scroll back up to the top, and we will replace this unused component import with useState. And down here inside of our constructor, we have three pieces of state that we will need to recreate.
[02:00 - 02:06] Check out count, loading, and error. So let's go ahead and create those one at a time.
[02:07 - 02:28] The first one is checkout count, which we will set equal to a state of zero. The second one is going to be loading, which we will set equal to a state boolean through.
[02:29 - 02:41] And the third one is going to be error, which we will set equal to a state of false. And with that, we will delete our constructor entirely.
[02:42 - 02:51] Now, don't worry too much about any of the red and yellow squiggles that you see. We will handle those in due time.
[02:52 - 03:06] So this state replicates the same state in the class component, but it actually makes it more flexible by making each piece of state independent of the other pieces. So if we need to alter just one piece of state, all the other state remains the same.
[03:07 - 03:13] And that's an awesome thing. Okay, so next thing that we're going to do is condense our lifecycle effects.
[03:14 - 03:29] Component did mount and update checkout count. If you notice in this file, this is actually duplicate code because we need to call this get checkout count function, both when the component first mounts, and then every time an item is added or removed from the checkout.
[03:30 - 03:40] So for this to work in our class-based component, we need to call the same code from both component did mount and update checkout count. So here is what our current code looks like.
[03:41 - 03:55] But with the react use effect hook, we can combine these two functions into one and just add the checkout count state variable to the use effects dependency array. So every time that that value is updated, the use effect will be called again.
[03:56 - 04:09] So to get us started, let's import use effect at the top of our function. And then we're going to replace the component did mount with a single use effect hook.
[04:10 - 04:19] And here is what it's going to end up looking like. We'll declare use effect and the callback function inside of it first and foremost.
[04:20 - 04:31] And then we are going to create a new function that we will call that checkout items because this is an asynchronous function. We can't call it straight from the use effect.
[04:32 - 04:39] We have to make a function inside of it that will actually make the async call. So we've got our async.
[04:40 - 04:59] And from here, we can just take our checkout items count, copy that, paste it in. And we can actually take all of this as well, copy it, paste it, add the closing curly brace so you don't forget that.
[05:00 - 05:09] And then instead of using this dot set state, we will use set checkout count. We will set it equal to the checkout items count.
[05:10 - 05:20] As long as checkout items count is a number or checkout items count is equal to zero. This evaluates to falsie, which is why I have to do a separate check for it.
[05:21 - 05:30] So as long as that's all good. Finally, we will call the fetch checkout items function that we just declared here.
[05:31 - 05:40] And we will add our dependency array of checkout count. So now you can completely delete component did mount.
[05:41 - 05:50] So if you were to check your locally running app in the browser now, you would see a reference error in the Chrome DevTools console. Update checkout count is not defined.
[05:51 - 06:04] Update checkout count was the function passed to our product list and our checkout components. So when either component adds or removes a product from the checkout, it alerted the app to check the new count of items in the checkout.
[06:05 - 06:25] Turns out we still need that function, but we can simplify it now that we've got the use effect function in here. Instead of duplicating the API call in get checkout count, we'll create a new boolean called cart updated that will keep track of if our checkout has been updated by either child component right underneath our state declaration.
[06:26 - 06:40] So right underneath our state declaration for checkout count, add the new cart updated state. So right here, we will have cart updated and set cart updated.
[06:41 - 06:58] And we will set it to false as well. And then we will go to our update checkout count, which we will turn into a const arrow function like our others, and we will dramatically simplify this.
[06:59 - 07:04] All it needs to handle now is set cart updated to true. That's it.
[07:05 - 07:09] We can remove the async. We can pretty much remove everything.
[07:10 - 07:21] And here is the final step that's going to tie all of this together now. We are going to add the cart updated boolean to our use effects dependency array.
[07:22 - 07:39] So every time this variable state gets updated by product list or by checkout components, calling update checkout count, it's going to re-trigger this use effect function to run and update the checkout count state variable. So we put cart updated in there and we save.
[07:40 - 07:57] Now one thing that I want to mention is don't forget to set the cart updated boolean back to false after this use effect runs. So when the boolean is set to true again by one of the child components, use effect recognizes that one of the dependencies that it's watching has changed and it needs to update.
[07:58 - 08:09] So right here, after our if statement, where we set our checkout count, we're also going to set our cart updated back to false. Not too bad.
[08:10 - 08:15] If we check back over in the browser, it now looks like our app is running again. Good deal.
[08:16 - 08:28] So we weren't able to completely remove the second function, but at least we were able to dry up our code and remove the duplicate calls to the checkout API. This code is simpler and it's easier to understand and I hope that you agree.
[08:29 - 08:45] Okay, with that taken care of, let's do a quick check of the functionality and make sure that it's the same. So go ahead and go back to your browser tab or open it up if you haven't been running it locally and let's head over to the product page and add a handful of products to the checkout.
[08:46 - 08:55] Okay, I'm just going to add three, four, five, six, you know, five or six things. Easy enough.
[08:56 - 09:01] All right, so the checkout count has correctly updated in the nav bar. Great.
[09:02 - 09:14] Check out page next and remove some items and make sure that the checkout count updates again, which it looks like it has once our toast clears the screen. Very good.
[09:15 - 09:21] Okay, last step for this component. Let's check our ES lint errors back over in our IDE.
[09:22 - 09:28] So if we come over to the problems tab, we have a few problems. Not to worry though, we can handle them.
[09:29 - 09:37] So it looks like we've got a few unused variables actually. It turns out that our error and loading variables aren't currently being used in this component.
[09:38 - 09:45] They were added because there's an API call happening here. And if it took a while to return or failed for any reason, these variables might be needed.
[09:46 - 09:52] But when I consider if these states are actually needed, I don't think that they are. So let's talk through this scenario.
[09:53 - 10:14] When the count of current checkout items is loading, the nav bar count doesn't need to display anything like a loading circle or something else that you might see on the products page, for instance. Likewise, if there's an error fetching the count of checkout items, I think I'd prefer not to show anything next to the checkout icon instead of a specific error icon of some sort.
[10:15 - 10:24] So let's go ahead and completely remove these two variables. And that takes care of those two ES lint errors.
[10:25 - 10:34] Now we have no problems. Okay, there is one thing that we haven't really covered yet, and that is in case there's an error when fetching checkout count from the API.
[10:35 - 10:54] However, because in production this will happen at some point, we'll need to add one more check to that if statement that we have right here inside of our use effect to handle this. So if the get checkout count endpoint throws an error, it needs to return this error constant of fetch checkout count error.
[10:55 - 11:07] So let's add the following code to the component. We are going to import fetch checkout count error from our constants.js file.
[11:08 - 11:30] So now that we've got fetch checkout count error, we will just add it as one more or statement into our if check here. So if checkout items count is equal to the fetch checkout count error, and we can tell that that's what it's going to return by checking out what this get checkout API actually does.
[11:31 - 11:41] And it's really helpful that VS code will actually show it to you without having to go to the function itself. So if we look at what this function does, it tries to go out and fetch from the checkout endpoint.
[11:42 - 11:49] But if it throws an error, the error message that's returned is the fetch checkout count error. So that's why we're checking for it here.
[11:50 - 11:54] And that should cover us for the error states. VS code does a little reformatting.
[11:55 - 12:02] And I think that we're ready to test that this error state works correctly. So we can test this in Chrome by blocking the network request.
[12:03 - 12:08] That's the easiest way to do that. And I will show you how now.
[12:09 - 12:18] Back over in your browser, go ahead and pop open the network tab inside of our dev tools. Scroll down here.
[12:19 - 12:28] Open it up and go ahead and refresh the page so that we can see all the network calls that are coming in. And then find the checkout call.
[12:29 - 12:37] This is the one that we're going to need to block. So if you right click on this, there's actually an option to block this request URL.
[12:38 - 12:41] And that's what we want to do. So go ahead and click that.
[12:42 - 12:55] And now we can see that we're blocking our call to get the checkout. And let's go ahead and head over to the hardware handler home page since we're only doing this for the checkout for right now and refresh the application.
[12:56 - 13:09] And we see nothing. So if we actually look at our network and do a little filtering down to just the checkout, we can see that it's been blocked.
[13:10 - 13:20] But even though it's been blocked, the app doesn't break. Instead, the checkout just sits here empty with nothing next to it, which is what matters.
[13:21 - 13:32] So go ahead and unblock the network request. You can either unclick this one to if you have multiple ones that you're blocking, refresh the app once more, and our little three comes back.
[13:33 - 13:43] So I would say that this component is done. So let's head to the checkout component next because this component displays all the items product manager plans to send to stores to sell.