The useCheckout Hook will do Double Duty in our App
Some custom hooks can help simplify code beyond the original components they touch.
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:06] All right, we're down to our last lesson in this module, Chalk Full of Custom Hooks. This should be a fun one too.
[00:07 - 00:25] It's definitely going to take some twists and turns that you may not see coming , but you'll be amazed where we end up when we're done with this lesson. This custom hook lesson will see us taking two slightly different checkout API calls and rewriting them into a single call that works for multiple components.
[00:26 - 00:33] I saved the best custom hook transformation for last. As I was looking over our code base, I noticed something that intrigued me.
[00:34 - 00:48] The app component and the checkout component have two very similar use effect functions. In App.js, the use effect is responsible for fetching and displaying the checkout count in the navigation bar next to the cart icon.
[00:49 - 01:05] The checkout.js file on the other hand, displays all the products and their pertinent details present in checkout at any given time. When we examine the code for each component, when it calls those checkout APIs, the similarities between them are hard to overlook.
[01:06 - 01:16] Here's the use effect in the app. Catch checkout items, get checkout, count, and then eventually end up displaying that.
[01:17 - 01:25] And then check out the use effect for the checkout component. Once again, we get all checkout items and then set those.
[01:26 - 01:35] Count of products in the checkout count in the nav bar and actual products in the checkout itself. Are you seeing where I'm going with this?
[01:36 - 01:44] I think that we can combine these two calls into a single hook that can do the job for both components. Ready to give it a try?
[01:45 - 02:09] Okay, let's refactor the use effect in the checkout component first into a custom hook because this function is the one that I think can be used to do double duty and take the place of our two separate API calls. The function calls the get all checkout items API endpoint and either sets the local variable of checkout items to the array of data returned or sets error to true.
[02:10 - 02:20] That's it. So once more, we will create a new custom hook in our hooks folder and we'll name this one use checkout.js.
[02:21 - 02:34] And inside of the file, we'll make a new placeholder function as well. And we will export use checkout as a named export.
[02:35 - 02:43] OK, so let's add some code to this hook. I will bring it over here so we can look at our checkout code at the same time.
[02:44 - 02:52] So if we look at the state variables, the use effect in checkout is using. They are checkout items, error, and loading.
[02:53 - 03:05] So like with our other hooks, we'll leave loading to the checkout component because the remove item from checkout function still needs to access and update it as well. But we can add those other two variables into our new hook.
[03:06 - 03:40] And remember to import the destructured use state at the top of the hook from React for this. So we will bring in two constants, checkout items, and set checkout items with a use state of an empty array, and error, and set error with a use state of false, and an import of use state from React.
[03:41 - 03:55] OK. Now I noticed something during this refactor that although I set loading to false after the remove item from checkout is called, I don't actually set it to true before the API call is completed.
[03:56 - 03:59] We should fix that. This is inside of the checkout.js.
[04:00 - 04:06] So add this single line to the checkout.js file right before we make the API call. Right here.
[04:07 - 04:12] There. That makes more sense.
[04:13 - 04:20] Now before the API call is made, we start loading. And once it is finished, whatever the result, we set loading back to false.
[04:21 - 04:21] OK. OK.
[04:22 - 04:32] Not quite done. And since the loading message will now show on initial component load and when items are removed from the checkout, the loading message text should probably change slightly.
[04:33 - 04:45] Instead of fetching items to checkout, it might be better as fetching items in checkout. Not an essential change, but I think that covers both possible loading scenarios better.
[04:46 - 04:54] So let's scroll over and find our loader message and change this from fetching items to checkout to fetching items in checkout. OK.
[04:55 - 04:58] Thanks for indulging me. Now onto the main parts of this hook.
[04:59 - 05:12] So the easiest way that I found to create a new hook is to copy the use effect from checkout into the hook and refactor it from there. The only line that we're actually going to need to remove from this is the loading line.
[05:13 - 05:42] So go ahead and copy that use effect, paste it in, and remove the set loading to false, and import what we need to import to make this work. Use effect, the fetch product checkout error, which we will auto import, and the get all checkout items, which I am going to import as a named import so that I don't have to use the wildcard import.
[05:43 - 05:53] OK. At the end of the hook, we'll return checkout items, set checkout items, and error as well.
[05:54 - 06:03] So why are we returning the set checkout items from the hook? I'm really glad that you asked that.
[06:04 - 06:25] This is a less discussed fact, but since hooks are hooks anywhere in React, we can return state setters the same way that we can return any other value from a custom hook. And because the checkout component resets the checkout items after removing an item from checkout, we need set checkout items from this hook, in addition to the checkout item state variable itself.
[06:26 - 06:32] OK, now we're getting somewhere. It's time to take our new hook and its new variables and update our checkout component.
[06:33 - 07:05] Inside of our component, remove the checkout items and error state, and then import our use checkout hook and replace the two use state variables with the values from the hook right inside of our component declaration. Import use checkout, and then checkout items, set checkout items, and error all equal to our use checkout hook.
[07:06 - 07:17] So the use effect that was previously responsible or directly calling the checkout API can also be simplified, like so. We will delete everything out of here.
[07:18 - 07:26] We will put in an if statement. So if checkout items or error, then we will set loading to false.
[07:27 - 07:32] And our dependency array will be checkout items and error. Cool.
[07:33 - 07:46] So since the hook is now responsible for the checkout items and the error states, all we need the use effect to do is update the local set loading variable in the component, once items or an error fetching those items come back. Nice and simple.
[07:47 - 07:59] And with that, we're ready to retest this app and make sure the checkout page in the app still works in good conditions and bad. So start up the app and then head over to your browser.
[08:00 - 08:05] Add a few items to the checkout via the My Products page. And then navigate on over to it.
[08:06 - 08:08] Seeing items in the checkout? Great.
[08:09 - 08:12] Remove an item or two from the checkout. Did they successfully disappear?
[08:13 - 08:16] Yep, sure did. OK, one last test of this component.
[08:17 - 08:27] The error state effecting items in the checkout fails to load. As we have done many times before, open up your dev tools and block the checkout API call in the Network tab.
[08:28 - 08:39] [VIDEO PLAYBACK] Looks like something went wrong fetching all products to checkout. Well done.
[08:40 - 08:48] Everything seems to be working as expected, no matter the status of the checkout API call. Now it's time to move on to our app component and refactor it.
[08:49 - 08:57] Let's do it. I am going to drag use checkout over here so that we can look at it and the app.js side by side.
[08:58 - 09:21] So my thinking for app.js is that we can actually replace this checkout API call that we're making to get the checkout count for the nav bar component with the custom hook that we made fetching all the products in the checkout component. Unlike our other component, our checkout, this API call just returns the number of items in the checkout instead of all their details.
[09:22 - 09:33] But there really aren't so many differences between this use effect and the one that we replaced in checkout that we can't make this work for them both. One of the main differences between the two functions is the state variable that they're returning.
[09:34 - 09:48] While checkout component needs an array of checkout items to render in its JSX, app only needs the checkout count. So let's create a new state variable inside of our use checkout hook with that checkout count state.
[09:49 - 09:59] Add the following line beneath our other two variables in the hook. Check out count and set checkout count.
[10:00 - 10:13] And we'll do a use state of zero because this is actually going to return a number. Before we continue with this hook refactor, I want to take a minor detour and rename a variable in app.js that's inaccurate.
[10:14 - 10:27] It's the Boolean variable set cart updated. While this does the job well enough to indicate to another developer what's happening, it's not using the same naming conventions that we're using in the rest of the application to refer to the checkout.
[10:28 - 10:31] So I want to rename it here. Here's the tip.
[10:32 - 10:48] When renaming variables, be explicit about it. Any developer that's reviewing a pull request should notice that the variable name has changed, but for devs who touch that code in the future, it sometimes makes sense to put in a comment or something where the renamed variable is just for quick reference.
[10:49 - 11:03] Trying to prevent confusion or myself and for others in the future is always a high priority in my mind. So since everywhere else, we refer to that component as the checkout component, I think that the variable signaling something has happened in the checkout.
[11:04 - 11:19] Either an item has been added to it or removed from it should be named set checkout updated. I know this is a small change, but how a development team defines and names things that they all use is essential to ensuring that everyone is on the same page and aware of what's going on.
[11:20 - 11:40] Yes, cart updated is close, but checkout updated is just that little bit more accurate in this scenario, since we call our cart component checkout everywhere in this app. So please change all the instances of cart updated and set cart updated inside of the app.js file to be checkout updated and set checkout updated.
[11:41 - 12:00] If you're using VS Code as your IDE, I'll show you a cool trick to refactor variables in a jiffy. To update a particular variable name in all places in a file where it's present, select the word to rename and type command shift l on a Mac or control shift l on Windows, and then just type in the new name you'd like to use instead.
[12:01 - 12:09] Now all the places that the old variable was previously referenced in that file should be updated to use the new variable name. Pretty slick, right?
[12:10 - 12:27] Okay, sorry about the tangent above, but part of improving this app means improving already existing variable names to more accurately reflect what they're for. Anyway, back to our original task at hand, making our new custom hook use checkout, work for both the checkout and the app components.
[12:28 - 12:51] Since we want to rely on the original use effect in this hook to fetch the checkout item state, we don't really need to make an API call to get checkout count any longer. And since we don't need to make the asynchronous API call anymore, we don't need to declare a function to wrap the async call, call it at the end of the use effect, or worry about an error message.
[12:52 - 13:02] That will be handled in our other use effect. All this function really needs to know is if the value of checkout items changes, and if the length of the checkout items array is zero or greater.
[13:03 - 13:35] So if we copy this use effect over into our hook, and we paste it right underneath our first use effect, we are going to dramatically simplify this into an if statement. So if number checkout items dot length or checkout items dot length is equal to zero, then we're going to set checkout count to checkout items dot length.
[13:36 - 13:47] And our one dependency will be checkout items. We're not quite done yet though.
[13:48 - 13:58] Where should the old use effects checkout updated dependency go? We still want the count of checkout items to update each time a product is added or removed from the checkout.
[13:59 - 14:12] Well, it should become a dependency in the use effect where we call the checkout API right above this newly declared one. That way, anytime the value of checkout updated changes, this checkout API call will happen again.
[14:13 - 14:23] And when it refetches all the checkout items, that change to the checkout items variable will trigger the second use effect to run. And that will set the checkout count state.
[14:24 - 14:28] Nice chain reaction of events, right? So here's the final code that we should have.
[14:29 - 14:38] We are going to add checkout updated right here. We are going to bring checkout updated in.
[14:39 - 14:49] And finally, we are going to return checkout count. Good deal.
[14:50 - 14:54] Excellent. We're ready to use our use checkout hook within our app component.
[14:55 - 15:02] The first thing that we'll do is remove the checkout count state declared in app.js. And then we'll import our use checkout hook.
[15:03 - 15:22] And we'll destructure its checkout count variable to replace our local state. Don't forget to pass checkout updated into this use checkout.
[15:23 - 15:27] Then we'll refactor our use effect in app. It's about to get a lot simpler.
[15:28 - 15:49] Instead of having to call the checkout API and handle what it returns, we can simply watch the checkout count value supplied by the custom hook. Whenever the checkout count changes, whether from items being added or removed from the checkout or from the app initially loading, the use effect will run and set the local variable of checkout updated to false.
[15:50 - 16:05] Regardless of if the checkout updated variable is changed or not, we'll make sure that it's reset for whenever a user does trigger the update checkout count function that would change the state of checkout updated. So here is what our new use effect should look like.
[16:06 - 16:28] Delete all of that and this extra curly brace that's hanging out and we'll say if checkout count exists, set checkout count updated to false and the only thing that we need to watch is checkout count. How simple is that?
[16:29 - 16:39] Look how much cleaner it is. Okay, so refactoring this component to use this hook presents us with another opportunity to clean up some now unnecessary code.
[16:40 - 16:58] The only place that we were using the get checkout count function from the checkout API and the fetch checkout count error message was inside of the app component. Since our hook now handles getting checkout items, we no longer need to import the API call or the error constant inside of app.js.
[16:59 - 17:11] So let's delete both of these lines. Next up, now that we've deleted the one place where that API call was used, we can remove it from our checkout API.js file.
[17:12 - 17:23] So open up our checkout API file inside of our services folder and delete the get checkout count function. Bye and delete the import for fetch checkout count error.
[17:24 - 17:34] Don't need you any longer. And finally, open up the constants.js file and delete the fetch checkout count error from here as well.
[17:35 - 17:42] Sweet. So at this point, if you're running the app locally, you should be seeing an error in the console from the navbar.js.
[17:43 - 17:49] It's looking for that fetch checkout count error constant that we just deleted from the app. This is a simple enough fix though.
[17:50 - 18:07] Open up navbar inside of our components.js And on line 25 of this file, we're checking if the checkout count prop being passed from the parent component is the error message. Since we refactored the component to use the hook, this error state will never occur.
[18:08 - 18:18] So now this conditional line can be simplified too. Go ahead and remove checkout count does not equal fetch checkout count error part of the JSX.
[18:19 - 18:26] Delete that. With that gone, we can now delete the import for the error constant on line four as well.
[18:27 - 18:35] Man, look at all that that we've deleted. After this change, your app should begin compiling again, and we can test out our new refactor's functionality.
[18:36 - 18:42] So restart the app if it's not already running and open it up in your browser. Here's my running instance.
[18:43 - 18:50] And if I refresh the page, we have some items that are in our checkout. Let's add a few more items.
[18:51 - 19:05] And make sure that the count updates each time, which it looks like it is, and do the same test removing a few items. Looks like we're good.
[19:06 - 19:16] Now let's test our error scenario when we can't fetch checkout items and get an account to display in the nav bar. Block the checkout API and refresh the page.
[19:17 - 19:19] And checkout is still there. There's just no count.
[19:20 - 19:22] Nothing broke. Looking good.
[19:23 - 19:34] So one last thing, if we pop back over into our IDE, there is one ES lint error that's still hanging out. It was identified when we modified the nav bar component, actually.
[19:35 - 19:45] There's a missing prop type validation in nav bar. Because we opened the file to modify it, my ES lint BS code plugin went to work and found this error.
[19:46 - 19:54] So we've encountered this error many times before at this point, so you should have a good idea of how to fix it. We will import prop types at the top of this file.
[19:55 - 20:13] And then at the very bottom of the file, underneath the export, we'll add the proper validations for the checkout count prop. Nav bar prop types is equal to an object checkout count.
[20:14 - 20:24] And then the prop type dot number dot is required. And that fixes our ES lint problem.
[20:25 - 20:29] No more errors. We've tested, we've resolved ES lint errors all as well.
[20:30 - 20:46] Man, this has been a long lesson I know, but look at what we have accomplished. Not only were we able to create a single custom hook that worked for more than one component, but we were also able to remove an entire API call and error message and a couple of complex use effects.
[20:47 - 20:53] Excellent work making it through. I hope that you're as proud as I am all that we've accomplished thus far.