Refactor ProductList.js

We saved the best (i.e. the most complicated) component for last. The product list, with its filters and products, is no match for our newly honed skills though.

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:16] The product list component can be intimidating at first glance because it displays all the products currently available and it provides the ability to filter them by department and brand. But it's really not that bad once we break it down using our recipe to refactor it step by step.

    [00:17 - 00:31] We've got this. In this lesson, we'll employ all the Hooks experience that we've gained up to this point and all the best practices we've learned to convert a pretty complex app from classes to hooks.

    [00:32 - 00:40] Before we actually start refactoring the product list, let's take a quick look at it in Hardware Handler. If we go to the My Products page, this is the product list.

    [00:41 - 00:56] We have a list of products which we can add to the checkout and we have a list of filters that we can filter down what we're seeing in terms of the products. So this is what we are going to be refactoring from class components to using hooks.

    [00:57 - 01:13] So the first step, as we've done with every other component thus far, is to change product list from a function to a class. So we will take this class declaration right here and turn that into an inline arrow function.

    [01:14 - 01:24] We can also remove the import of React and component from the statement on line one. Neither of these imports are required any longer.

    [01:25 - 01:43] And we will take out the render method that we have down here for our JSX because we no longer need that either, nor these destructured elements. And we will take out this final curly brace because it no longer needs to be here either.

    [01:44 - 02:00] Okay, the next step is to swap out our class-based state for hooks instead. So right where we deleted our import for React, we're going to bring back the used state hook.

    [02:01 - 02:16] And then we are going to replace all of these individual pieces of state products, loading error, error message, etc., etc., into all of these used state hooks. So I have spared you the pain of watching me type all of those out.

    [02:17 - 02:23] And now we have all of our pieces of state into state hooks. So we're going to delete our original constructor now.

    [02:24 - 02:35] So that is a decent amount of state variables, but there's one more variable that we need to add. On line 88 of our current component, we call the update checkout count prop.

    [02:36 - 02:45] So we need to bring that into our application. We can destructure it and pass it into this component just the same as we would do with any other state.

    [02:46 - 02:56] So delete this dot props, copy update checkout count, and up here, wrap it in curly braces and bring it in. Very good.

    [02:57 - 03:03] We'll come back to fix the remainder of that function soon. Let's try and be methodical and keep moving down the component as we refactor.

    [03:04 - 03:16] The next thing that we're going to do is split up this large, large, large component did amount function into smaller use effect hooks. So bring in the use effect hook into our component.

    [03:17 - 03:34] And this is a good opportunity to split up this component into two smaller use effect hooks because if you look closely, it's actually making two unrelated API calls . It's calling the product API to get all the products to display on the page and the department API to make filtering the products by the departments possible.

    [03:35 - 03:42] Previously, these calls all had to be together because the lifecycle component dictated it, even though they are unrelated. But now we don't have to.

    [03:43 - 03:55] Now we can group these calls into separate hooks and only trigger and update their state when the variables that they depend on change. So let's refactor the get all products API call first as it's a little bit more complicated.

    [03:56 - 04:03] We've already imported use effect at the top of our component. So the next thing that we're going to do is declare an asynchronous function.

    [04:04 - 04:19] And I will just do that right here. And we're going to extract the product API get all products call into a function contained within it, which we're going to name that products.

    [04:20 - 04:34] So we will go and copy this and bring it down here. You will name this all products and similar to our original component did mount .

    [04:35 - 04:46] If all products is equal to the fetch product data error. So if the call fails, we're going to set our error to true.

    [04:47 - 05:05] And we're going to set our products to all products, which is actually the error message in this case. Now, if it succeeds, we're just going to set products to all products once again, which will actually be products at this point.

    [05:06 - 05:14] And then we are going to bring in the filter by brands. All of this we're going to take with us.

    [05:15 - 05:22] So we'll copy that. We will paste it in.

    [05:23 - 05:28] We will replace this products with all products. The rest of this can pretty much stay the same.

    [05:29 - 05:49] And right under that, we will finally set filters by brand with our all filters brand, which I need to rename up here, all filters by brand. And last but not least, we will set loading to false for this one.

    [05:50 - 06:01] And finally, we will fetch our products, call this function that we just made. And we'll put our empty dependency array that will fetch it on component load.

    [06:02 - 06:08] Go ahead and give that a save and VS code and prettier should do their thing. What this code is doing isn't too complicated.

    [06:09 - 06:25] It just looks intimidating because of the format filters function that pulls brand names to add as filters after the products are fetched. So the second use effect that we're going to write is forgetting all of the departments that these products live in, which should actually be the easier function to extract from component did mount.

    [06:26 - 06:39] So as with the previous section, we are going to create another new use effect right after our first. And the new function inside of it, we will call fetch filters.

    [06:40 - 06:54] We are async function. And filters by department is what we will call the new variable that gets returned when we call the get all departments API.

    [06:55 - 07:16] And if filters by debt is equal to the fetch department data error, so that call failed. Once again, we will set the error Boolean to true and we will set the error message to filters by doubt.

    [07:17 - 07:29] And if the call succeeds, we will set the filters by department to filters by doubt. And of course, set loading to false at the end.

    [07:30 - 07:39] And then we will fetch filters. And once again, this use effect will be given an empty dependency array because it needs to run on component mount.

    [07:40 - 07:58] And now we should be able to delete all of this code from our component did mount. So you may have noticed that we didn't actually handle the error scenario where both API calls throw errors that was in the original component did mount.

    [07:59 - 08:04] But don't sweat it for now, we will handle this particular error scenario later in this lesson. Very nice.

    [08:05 - 08:17] We have refactored our component did mount method into two separate use effect functions that better reflect the two unrelated API calls that they make. This is the very type of scenario that hooks was aiming to solve and it did.

    [08:18 - 08:24] Simpler, cleaner, clearer code. As we continue refactoring our component, the next thing to tackle is the other functions in product list.

    [08:25 - 08:31] The add item to checkout function and the on filter change function. So let's modify add item to checkout.

    [08:32 - 08:39] It won't require too much rework to become a regular error function. Change this into a function that will work in a functional component.

    [08:40 - 08:48] We will add a const in front of the function name. We will remove the two set states in the if else statement and we will replace those.

    [08:49 - 09:05] And we will set both loading and error variables to false at the end of this function. We have already updated the update checkout count prop so we don't need to worry about that one either.

    [09:06 - 09:23] That should knock this function out. And then the on filter change function takes in a filter option but the user has selected either a department or a brand name and checks if the current active filter array has filter in it.

    [09:24 - 09:35] If it's already present, it means that the user has unchecked the filter box so we must remove that filter value from the array of things to filter by. This is why we find the index of the filter variable.

    [09:36 - 09:58] Spread the existing state of the active filter array, splice out the filter value in the array at that index and then finally update the active filter state with the newly altered array. If the array does not currently contain the filter value, it adds the new value to the active filter array by spreading the existing active filter values into a new array with the new filter value.

    [09:59 - 10:39] So to refactor this one, we will add a const to the front of the function and locate all of the pieces of this dot state inside of the function and remove them. And where we set the active filter state, refactor it to update the individual variable with the set active filter function.

    [10:40 - 10:45] Not bad, right? Personally, I think that this is easier to read than the previous way that we set state.

    [10:46 - 10:56] Alright, we're getting towards the end of the initial refactor of this component. We're down to the filtering logic that determines which products are displayed on the page and the JSX display itself.

    [10:57 - 11:23] This filtering logic checks the active filter array and if it's either empty or it's equal to all of the brand name and department checkboxes being checked, then the local filtered list variable will be set equal to all of the products fetched from the get all products API call. If neither of those conditions are met, the products array will be filtered down by whatever active filters are present in the array and the results are set to the filtered list variable.

    [11:24 - 11:36] Regardless, the filtered list variable is what actually gets rendered by the JS X and it gets updated every time the active filter array changes. But the product array is never mutated after it's initially fetched from the API.

    [11:37 - 11:45] So now that we've talked about how this filter works, the logic should be quick to update. All we have to do is remove the this.state code in our filter.

    [11:46 - 12:05] That should be about all that needs doing here and we'll test it shortly to make sure. So we're going to add filtered list back.

    [12:06 - 12:18] We've finally made it to the JSX and this too shouldn't give us much trouble. To make it work, we'll go through the JSX, find all the instances where the this keyword is being used, either for component state or for functions and remove it.

    [12:19 - 12:58] The first two instances require almost exactly the same refactoring because they're the filters that filter products by department and/or brand name. Now there is one last piece of JSX to refactor and that is the product component which is rendered inside of our product lists JSX that takes in our add item to checkout function.

    [12:59 - 13:08] Our original product component is right here. The only thing that we need to do to make this work is take out this this.

    [13:09 - 13:21] And with that refactoring, we should be done. Okay, so I just stopped the recording for a second because my terminal was freaking out and I couldn't figure out what exactly was wrong, but I located it.

    [13:22 - 13:36] And the on filter change after we had made this function into an arrow function , I had forgotten to actually add the equals sign and the arrow sign. So you'll need to go back to your own and fix that in the code to make everything work again.

    [13:37 - 13:50] But now that we have finished updating this component and it's compiling, let's make sure that it still works correctly in the browser. So if you haven't already started up locally and navigate to the my products page to add a couple of items to the checkout.

    [13:51 - 13:58] Add a couple of items, try some different filtering. Looks like those are working.

    [13:59 - 14:01] Cool. It looks like things are working correctly.

    [14:02 - 14:11] So let's go after the remaining errors that we're still seeing in our VS code IDE. The first ES lint error that will tackle is the one at the bottom of this list.

    [14:12 - 14:23] Do not use array index in keys. If you check the ES lint error, the reasoning behind it is that using an array index doesn't uniquely identify the item if it were to be sorted and re-rendered.

    [14:24 - 14:43] While this particular linting error doesn't really apply to this list of brands , we'll never filter or sort them in any other order, it'll be a simple enough matter to resolve. So we'll remove the index value from the JSX where we map over filters by brand and use filter.value as the unique key for each checkbox in the list.

    [14:44 - 15:02] That's one error down, just a couple more to go. Our next ES lint error to resolve is the prop types error of update checkout count is missing in props validation.

    [15:03 - 15:13] We've seen this error before in the product list component, so we know how to fix this. And since we've already installed the prop types NPM library into our app, the solution is even simpler than before.

    [15:14 - 15:41] Once more, import the prop types at the top of your application and add the prop type for the checkout count at the bottom of the component. At this point, that error should have disappeared from the ES lint problems tab as well.

    [15:42 - 15:55] Alright, we'll resolve the last ES lint error in this next section when we test our error states in the browser. So there's one ES lint error that's still present, and it alerts us to unused variables in the multiple errors constant.

    [15:56 - 16:10] This is actually a good thing because it tells us that previously used code hasn't been included in our refactor. Since one of the unused variables is the multiple errors constant, it's a good indicator that we should test the error states in the browser from our API calls.

    [16:11 - 16:27] So let's head back over into our browser. And just like in previous lessons, we will open up our network tab, clear out our previous blocked calls, refresh this page, and we're going to find our API calls and block them.

    [16:28 - 16:47] So the ones that we're looking for are departments, block that, and credits, block that. If either the products or the department API calls fail, the error messages in the browser will show up as expected.

    [16:48 - 16:56] So now that I'm blocking products, I cannot load department filters. Please refresh the page or try again later.

    [16:57 - 17:11] When I block the products and refresh the page, something went wrong fetching all product data. So if both API calls fail, the multiple error message isn't displayed though because we're not using that constant.

    [17:12 - 17:22] We get something went wrong fetching department data instead. This is happening because now we've separated the component did mount that did both API calls together into two separate use effect functions.

    [17:23 - 17:26] So how do we fix this? Apply with one more use effect of course.

    [17:27 - 17:37] Let's head back over into our IDE. So to fix our component in the case that both API calls and product list fail, we're going to create a third use effect.

    [17:38 - 17:53] In order for this to work, we'll need to also adjust our existing use effects instead of setting the error message variable in each of them. If an API call fails, we will set the products and the filters by department variables to this error message.

    [17:54 - 18:09] So the first use effects code fetching product data will go from set error message to set products. And likewise, our second use effect will go from set error message to set filters by department.

    [18:10 - 18:21] And now we can create that third use effect. So inside of this use effect, we'll check if the error function is true.

    [18:22 - 18:46] And then we will nest another if statement that if products is equal to the fetch product data error and filters by department is equal to the fetch department data error, then we should set the error message to the multiple errors constant. And then we will have an else if.

    [18:47 - 19:16] So if just products is equal to the fetch product data error, set the error message to our fetch product data error. And finally, if our filters by department is equal to the fetch department data error, we will set the error message to the fetch department data error.

    [19:17 - 19:26] And we will make our dependency array watch products filters by department and error. Okay.

    [19:27 - 19:41] So let's test these error states in the browser once again. So give the page a refresh after adding the new use effect while still blocking both API calls in the dev tools network tab.

    [19:42 - 19:44] That looks much more appropriate. A lot of things went wrong.

    [19:45 - 19:51] Please try this again later. Oh, and that eslint error about unused variables in your ID.

    [19:52 - 19:53] It's gone. Sweet.

    [19:54 - 20:02] So I think this component refactor is finished. I know this was a long lesson, but not only did we make it to the end of the lesson, but we've also made it to the end of this module.

    [20:03 - 20:09] Excellent job. I hope you feel more confident now about updating existing class components with hooks.

    [20:10 - 20:15] And if you still feel a little shaky or unsure, don't worry. We've got custom hooks coming up in the next module.