Our First Custom Hook: useDepartments
We'll write our first custom hook to fetch department info for our application in one centralized place.
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:14] If you look through our code, one thing you might notice while checking out the various components is that there are duplicate API calls happening. Multiple components call the same API end points in our app to retrieve information.
[00:15 - 00:33] Situations like this are a perfect opportunity to refactor some code into a custom hook and simplify the code contained in our components in the process. In this lesson, we'll make our first custom hook. We'll learn how to extract code from one component and recreate it in a centralized and reusable hook.
[00:34 - 00:49] Just like with our previous module lessons, we'll start with a simpler custom hook and work our way up to the more complex ones in future lessons. So if you need a copy of the sample app, before we begin adding custom hooks, you can download it here from the lesson.
[00:50 - 01:10] The first duplicate API call that stood out to me is the get all departments call. It's called in both the product list and the product form components after our updates to these components in the last module. This seems like as good a place as any to start. Let's make this call into a custom hook that we can share with all the components that need it.
[01:11 - 01:28] Because if you look closely, this use effect is fetching all the departments in product form. And this use effect is fetching all the departments in product list. So to keep our project organized, let's make a new folder inside the client source folder named hooks.
[01:29 - 01:46] And once the hooks folder exists, we can create a new file inside of it called use departments.js. Following the rules of hooks, which we covered in the module introducing hooks, we name all custom hook files by starting with the word use xyz.
[01:47 - 02:01] And since this hook is about fetching departments, we'll name it use departments.js. Alright, now we're ready to start adding code to this hook. So as I said before , there are two components in our app that are calling the department API, product form and product list.
[02:02 - 02:17] There are some slight differences between these two components, but not so many that we can't make a hook that can work for both situations. We'll just take it step by step. So looking at the code, we can see that both API calls have a few state variables in common.
[02:18 - 02:28] I'll put them side by side so it's easier to compare. Both of them share a department's constant, and they also share an error state variable.
[02:29 - 02:44] So let's define our hook function and import the use state hook into our file and add those two state variables inside of it. So we will import use state from react.
[02:45 - 03:03] And then we will create a new const that we'll call use departments, the same name of the function as the name of our hook. And inside of it, we are going to declare a department state and set departments with an initial state of an empty array.
[03:04 - 03:18] And we're going to declare an error and a set error state with an initial state of false. And then of course, we need to export this so that we can actually use this hook in other places in the future.
[03:19 - 03:28] Save that, and we're good to go. So if we look back over at either product list or product form, we can see that there is a function called fetch departments.
[03:29 - 03:40] And after that, the next piece of code to add is the fetch department's call inside of our use effect hook. You can lift and shift the entire use effect out of the existing product form with almost no changes and paste it into the hook.
[03:41 - 03:49] So let's do that now. All right, we take our entire use effect, copy that, and paste it into our hook .
[03:50 - 04:00] Now, there are a couple of things that need to be imported in order to work in this hook. We need to bring in the fetch department data error constant, and we need to bring in the department API.
[04:01 - 04:12] I'm going to just bring in the destructured version of the department API, though, to save myself a little bit of typing. So bring in get all departments, and then we can delete this entirely.
[04:13 - 04:20] Okay, we're making good progress so far. The next thing that we're going to need to do is remove the set loading state in this function.
[04:21 - 04:39] When you look at the two components that are calling the department API, both have a loading variable that displays some sort of loading spinner until the data has been fetched and transformed. Once the data is returned, the loader is set to false, which allows the component to render with the data that it retrieves, or the error message it displays if that call has failed.
[04:40 - 04:46] Now, you may be wondering, why don't we just declare a loading state variable inside of this hook as well? We could.
[04:47 - 05:02] That could work, but in each component, that same loading state is used in multiple places inside the component, not just for this API call alone. In this case, it makes more sense to remove the set loading variable from this hook and let the loading state be handled in the components themselves.
[05:03 - 05:16] And finally, we need to return either the department state of that we fetched or the error that's occurred. So right after the use effect has made the API call, we'll return both state variables to the component as a destructured pair.
[05:17 - 05:22] So here's what we're going to do. So let's remove the set loading function right now.
[05:23 - 05:36] And at the very bottom of this, let's add in a return statement where we will return both departments and error. And of course, don't forget to import use effect as a hook in this function.
[05:37 - 05:47] Next, we can start integrating this custom hook into our components and removing their unique department API calls. So let's replace the API call in our product form component first.
[05:48 - 05:54] It should be the easier of the two. So looking once again at our use effect, this is what it currently looks like.
[05:55 - 06:04] To replace this with our hook, we're going to take the following steps. At the top of our function, we are going to import our new hook of use departments.
[06:05 - 06:22] And then in the code where we declared the department and error variables separately, replace those two lines with the following line of destructured variables from the use departments hook. So we'll bring in departments and we'll bring in error.
[06:23 - 06:32] And we'll set that equal to use departments. So in this way, our custom hook will now be responsible for the departments and the error states.
[06:33 - 06:41] And we can now refactor our use effect like so. We won't completely delete it, but we will cut it down a good bit.
[06:42 - 06:59] So now we'll have an if statement that says if departments dot length is greater than zero, or there's an error, then set loading to false. And our dependency array will now be watching departments and error.
[07:00 - 07:13] So all the use effect inside of our product form needs to do now is check if either the department's variable is an array, or the error variable is true. If either of those conditions is met, the loading variable in the component gets set to false.
[07:14 - 07:22] The use effect gets triggered anytime the variables in the dependency array change. In this case, when either the departments or the error variables change.
[07:23 - 07:32] So at this point, we can also remove the department API import at the top of our file. This components code looks much cleaner now, right?
[07:33 - 07:47] We'll test this functionality after we've refactored the other component to use our new hook too. So the product list components department API call is slightly more complex than the one in product form, but it's nothing that we can't handle.
[07:48 - 08:07] Here is what it currently looks like. And just like with the previous components, we're going to import our use departments hook at the top of this component. And then underneath where all of our state variables are declared and instant iate our hook and the values that it's responsible for.
[08:08 - 08:16] So once again, it will be departments and error. And we'll set that equal to use departments.
[08:17 - 08:25] There is one issue which you'll notice right here. Error has already been declared because we've already got an error state variable present in our component.
[08:26 - 08:47] Luckily, we can rename the error to something like department error on import and then we'll be able to set the components local error state true manually in our refactored use effect hook. Okay, so now we can head down to our use effect hook and we're going to clear out all of this.
[08:48 - 09:21] And instead, we're going to say if departments dot length is greater than zero, set loading to false and set filters by department to departments. And then make another if statement that says if there is a department error, once again, set loading to false, set error to true, and set filters by department to the fetch department data error.
[09:22 - 09:46] And then our dependency array will be departments and department error. So we can't condense this one quite as much as the other component because if there's an error in the API call, in addition to setting the local error Boolean, we have to set an error message to display in the browser, while still taking into account if the other API call to fetch products in this component fails.
[09:47 - 09:58] But that's okay. This code is still clearer to read and understand than it was previously, and we may be able to further simplify it when we write a custom hook for the product API call in our next lesson.
[09:59 - 10:08] Once again, we can remove the department API import at the top of this file. Okay, cool, we have both components using our new custom hook.
[10:09 - 10:15] It's time to test that the app still works as expected. Let's head over into our browser.
[10:16 - 10:34] So if your app isn't already running locally, it's time to fire it back up, and let's test out the product form first. We head to add new products, we check the department drop down, and we can choose a department, so that looks very promising, just like it was before.
[10:35 - 10:39] And this is the beauty of true refactoring. Previous functionality is maintained.
[10:40 - 10:47] Only the underlying code implementation changes. One thing to note is that you should use tests as a gut check.
[10:48 - 11:04] If we already had integration or end-to-end tests, that would be another good indicator that our refactoring efforts were successful. As long as the tests continue to pass, after we're done switching out the API call with our new custom hook, we can feel reasonably confident that our code is doing the same thing.
[11:05 - 11:12] Since we haven't added tests yet, however, we'll have to rely on manual smoke testing for now. Just keep this in mind when modernizing your own apps.
[11:13 - 11:27] Now, let's also test the error state for this component. Like we've done many times before, go ahead and open up your Chrome DevTools, move to the Network tab, and it looks like I've still got a department request walking there.
[11:28 - 11:34] So let's go ahead and turn that on, and let's refresh the page. And something went wrong fetching department data.
[11:35 - 11:38] Perfect. That is just what we want to see in this scenario.
[11:39 - 11:55] Okay, so let's test the same hook on the My Products page. Re-enable all of your API calls, head over to My Products, and when everything is working, this is what we should see, products and their filters, including the filters by department.
[11:56 - 12:06] Go ahead and block that same department request URL again, refresh the page one more time, and something went wrong fetching the department data. That is the correct error message.
[12:07 - 12:12] So excellent. Everything appears to be working in each component just as it was before.
[12:13 - 12:24] So we just made our first custom hook. Typically, this is the part of the lesson where we'd address any remaining ES- led errors, but after touching these three files, there actually are no new errors to address.
[12:25 - 12:26] How great is that? So congrats.
[12:27 - 12:32] We just built our first custom hook, and we put it to work in the app. Was that as difficult as you thought it might be?
[12:33 - 12:38] I sure hope not. Custom hooks aren't really as intimidating as they might sound at first.
[12:39 - 12:47] In our next lesson, we'll build on what we've just done and tackle creating another new custom hook. This one is going to be focused on the product API.