Simplify Complex Components with the useProducts Hook

Custom hooks are good for more than just drying up duplicated code; they also help simplify our components.

Project Source Code

Get the project source code below, and follow along with the lesson material.

Download Project Source Code

To 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.

This video is available to students only
Unlock This Course

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.

Thumbnail for the \newline course The newline Guide to Modernizing an Enterprise React App
  • [00:00 - 00:13] In our last lesson, we took the department API call happening in multiple components in our app and reduced it down to a single custom hook that both of our components could use. And that's a great use case for a custom hook.

    [00:14 - 00:36] But here's another great use case for custom hooks, simplifying complex components. As developers, we walk a fine line between too much code abstraction, which makes code hard to follow, and too little, which leads to large, unwieldy files hundreds of lines long, with logic that's too closely tied to the component that is meant to display that data.

    [00:37 - 00:44] But it's not an impossible task. With more practice and experience, we can only get better at towing that line successfully.

    [00:45 - 01:04] So this lesson will focus on simplifying a couple of our more complex components by creating a custom hook that can handle the detailed logic and data transformations outside of the components, actually leveraging that data. We do a lot of product related data transformations in the product list component.

    [01:05 - 01:11] A component that feels particularly complicated and could use some simplifying is this component. There's a lot happening in here.

    [01:12 - 01:34] When we look closely at the code, calls to the product API are really contributing to its complexity. Since this is the case, I think we can do some code extraction and simpl ification through the use of a new custom hook responsible for calling and transforming product data for our component, so that any component now or in the future that needs that info can look to this hook to deliver it.

    [01:35 - 01:42] So let's look at how we can get started on this hook. In product list, we fetch products and make dynamic filters based on their brand names.

    [01:43 - 02:01] To create our hook that's focused on the functionality, we need to lift and shift out of the product list component. Currently, this component calls the product API to fetch all the products to render in the browser, and it uses these products to make brand filters where users can filter their products by brand name.

    [02:02 - 02:13] This is a big function. This use effect is giant, and really product list doesn't particularly care about how it gets the products or the filters by brand, just that it ends up with them.

    [02:14 - 02:25] So let's see about moving this functional logic out of this component. So just as we started with the use of departments hook, by creating a new file, we'll do the same for our new products hook.

    [02:26 - 02:41] Back inside of our hooks folder, where our other custom hook lives, create a new file named useproducts.js. This is where we'll move our product API call, distancing it from the components that need the data.

    [02:42 - 02:51] We'll also go ahead and put in the shell code of this hook after defining the file. For now, just add a very basic hook outline, we'll add onto it in the next section.

    [02:52 - 03:05] So let's name our hook useproducts, same name as the file, and export it. And of course, don't forget to finish this function.

    [03:06 - 03:16] Okay. When we look at what the product list needs in terms of state from the component, it looks like it needs product, error, and filters by brand.

    [03:17 - 03:25] These are the variables that are referenced in the use effect that we want to refactor out. So we'll add these same state variables to our useproducts hook.

    [03:26 - 03:39] So I'm just going to go up to the top here where I've defined them and copy them. Product, error, and filters by brand. Copy that, paste it in.

    [03:40 - 03:52] Remove the loading, remove the error message, and import the use state hook from react. Okay, making progress.

    [03:53 - 04:10] So if you're paying close attention, you may have noticed that I didn't include the loading variable here, even though it's in our original use effect right there. I decided to omit it and leave it to the product list component to manage because that same state variable is referenced in the component by other variables.

    [04:11 - 04:25] If this use effect was the only one using loading, I'd probably refactor it out into the hook as well, but since it's not, it's easier to let the product list component keep track of loading instead. This is the similar decision that we made for the use department's hook too.

    [04:26 - 04:37] So once that's taken care of, we can bring in the use effect from the product list. Now we'll bring in the whole use effect responsible for fetching data from the product API into this use product hook.

    [04:38 - 04:43] So go ahead and import use effect at the top of our hook. We will need that.

    [04:44 - 04:56] And copy this whole use effect to paste into use products. Okay, so we have a couple of helper functions that we also need to import.

    [04:57 - 05:10] I'm using the handy auto import that VS Code offers. And then I will just bring in this get all products as a destructured import.

    [05:11 - 05:20] And we can remove this. And we will remove our set loading because we're not using that.

    [05:21 - 05:35] And at the end of the function, we need to return something from our hook. So like with use departments, we are going to return products, filters by brand and error.

    [05:36 - 05:48] And that should be about all we need to do. So luckily, we've only had to make very minor changes to this use effect in order to get it to work in our custom hook versus how it was working in the product list component itself.

    [05:49 - 06:00] Now it's time to turn our attention back to the container component and bring this hook into it. So just like we did when we brought in the use department's hook, we're going to import this hook at the top of our file.

    [06:01 - 06:09] Use products. Sometimes the auto complete doesn't help.

    [06:10 - 06:19] Sometimes it does. Okay, so we have used products and then we declare it and pull out the objects that we need.

    [06:20 - 06:33] So products, filters by brand and error, which we also need to rename because it already exists in this file. And we need to keep track of it still.

    [06:34 - 06:40] And we will just call that all from use products. Once again, we must rename the generic error.

    [06:41 - 06:51] So the hook returns to the more specific product error to prevent a variable naming collision inside the component. There is still a reason to keep the original error state though, which we'll get to shortly.

    [06:52 - 06:56] So next, focus on the use effect. Go ahead and delete everything inside of it.

    [06:57 - 07:08] Yes, I said everything. Now that our custom hook is handling, fetching the products and formatting the filters by brand, we don't need to worry about any of that in this use effect.

    [07:09 - 07:31] All we still need to care about is if an error occurred while fetching products and that the filters by brand value is not an empty array. So now we can just say if products dot length is greater than zero, which means we've gotten back an array of products, we can set our loading to false inside of a separate if statement.

    [07:32 - 07:48] If we have a product error, we will set our error to true and once again set our loading to false. And then products and product error are what our use effect needs to look for now.

    [07:49 - 08:03] This use effect function only has products and product error in its dependency array and it's only checking the values for those two conditions, but we're also returning filters by brand from the hook. So why doesn't that also need to be part of the hooks dependency array?

    [08:04 - 08:25] Well, because filters by brand can't be created without a successful API call returning products from the API. We know that without products the filters can't be determined, so by checking that products returns an array with data, we can safely assume filters were created from product data that's present as well, meaning that we don't have to explicitly check for them.

    [08:26 - 08:37] If you'd like to, you're welcome to, but I'm just going to skip it. And since our hook is now returning products data and filters by brand, we can remove the state variables in the component by the same names.

    [08:38 - 08:41] Go ahead and delete these. Products.

    [08:42 - 08:49] Filters by brand. Just look at how much less code there is to try and understand in this component.

    [08:50 - 09:01] We've refactored a bunch of code out and we've really improved the readability. And because we can have multiple use effect functions only focused on what effects them, it's relatively easy to do.

    [09:02 - 09:08] This makes a huge difference in my opinion. I think we're ready to retest the app and make sure that it still works now.

    [09:09 - 09:17] So if your app is not already up and running, let's go ahead and restart it. I have an error, it seems.

    [09:18 - 09:25] Use products is not exported from hooks use products. Well, let's debug that.

    [09:26 - 09:31] Export default use products. Did I mistype this?

    [09:32 - 09:37] Remove that product API? Remove that format filters?

    [09:38 - 09:45] Use products from hooks use products. Well, that's interesting.

    [09:46 - 09:54] So I just had an error that I couldn't figure out. And it was because I had exported the use products hook as a default.

    [09:55 - 10:08] However, I apparently did not do that with the use department's hook. So when I tried to import it, not as a default, but as a named variable like this, it wouldn't compile.

    [10:09 - 10:18] But when I switched over to auto import and it imported it like that, everything is good. So that's an interesting bug that I just hit, but you might encounter it as well.

    [10:19 - 10:27] So I think I'll probably leave it in this video. Okay, so let's head back over to our browser now that our app is finally compiling.

    [10:28 - 10:36] First thing that we're going to do is navigate to the My Products page and see if everything loads. So far, everything is loading.

    [10:37 - 10:46] If we click just a few of these brands, looks like we get some different products that show up. That looks good.

    [10:47 - 10:55] And once more, go ahead and open up your network tab. We'll head over here and we're going to block the products call now.

    [10:56 - 11:03] Enable network request blocking and turn on the blocking for products. And go ahead and refresh the page.

    [11:04 - 11:08] Something went wrong fetching all of our product data. Very good.

    [11:09 - 11:18] When I look back in my product IDE, it doesn't appear that there's any new ES l int errors after the refactor either. So I think that we can call this component done.

    [11:19 - 11:29] And with that, we've written a second custom hook, which simplifies the code our product list is now responsible for and makes it easier for other developers to see what's going on in the component. Great job.

    [11:30 - 11:39] In the final lesson of this module, we'll add one final custom hook to really drive home how custom hooks works. This one's going to be for our checkout API.

    [11:40 - 11:59] [ Pause ]