Our First Custom Hook: useDepartments
We'll write our first custom hook to fetch department info for our application in one centralized place.
Custom hooks may sound intimidating at first, but useDepartments
should change your mind#
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 endpoints in our app to retrieve information.
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.
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.
Sample code zip file
If you need a copy of the sample app before we begin adding custom hooks, you can download it here.
We make duplicate calls to the department API#
The first duplicate API call that stood out to me is the getAllDepartments
call. It's called in both the <ProductList>
and the <ProductForm>
components after our updates to these components in the last module.
This seems like as good a place to start as any — let's make this call into a custom hook that we can share with all the components that need it.
Create a new hooks
folder#
To keep our project organized, let's make a new folder inside the client/src
folder named hooks/
.
This is where we'll hold all the shared React Custom Hooks that our app will utilize.
Make a new useDepartments.js
file#
Once the hooks/
folder exists, we can create a new file inside of it called useDepartments.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 useXyz.js
, and since this hook is about fetching departments, we'll name it useDepartments.js
.
Okay, we're ready to start adding code to this hook now.
Review the code currently fetching departments#
As I said before, there are two components in our app that are calling the department API: the <ProductForm>
and the <ProductList>
components.
The useEffect
in <ProductForm>
calling the getAllDepartments
API call currently looks like this.
const [loading, setLoading] = useState(true);
const [error, setError] = useState(false);
const [departments, setDepartments] = useState([]);
useEffect(() => {
const fetchDepartments = async () => {
const allDepartments = await departmentApi.getAllDepartments();
if (allDepartments === FETCH_DEPARTMENT_DATA_ERROR) {
setError(true);
} else {
setDepartments(allDepartments);
}
setLoading(false);
};
fetchDepartments();
}, []);
And the useEffect
in <ProductList>
calling the same API endpoint currently looks like this.
First, a snapshot of the component's state (this one has quite a few more state variables):
const [products, setProducts] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(false);
const [errMsg, setErrMsg] = useState('');
const [filtersByBrand, setFiltersByBrand] = useState([]);
const [filtersByDepartment, setFiltersByDepartment] = useState([]);
const [activeFilter, setActiveFilter] = useState([]);
And the actual useEffect
fetching the departments.
useEffect(() => {
const fetchFilters = async () => {
const departments = await departmentApi.getAllDepartments();
if (departments === FETCH_DEPARTMENT_DATA_ERROR) {
setError(true);
setFiltersByDepartment(departments);
} else {
setFiltersByDepartment(departments);
}
setLoading(false);
};
fetchFilters();
}, []);
There are some differences between these two components, but not so many we can't make a hook that can work for both situations. We'll just take it step by step.
Compose the hook#
Looking at the code above, we can see both API calls have a few state variables in common, in addition to the API call itself: they both share the departments
and error
state variables.
So let's define our hook function, import the useState
Hook into our file, and add those two state variables inside of it.
import { useState } from 'react';
const useDepartments = () => {
const [departments, setDepartments] = useState([]);
const [error, setError] = useState(false);
};
export { useDepartments };
So far, so good.
Move the fetchDepartments
function into the hook#
After that, the next piece of code to add is the fetchDepartments
call inside of the useEffect
Hook.
You can lift and shift the entire useEffect
out of the existing <ProductForm>
with almost no changes and paste it into the hook.
Pop this code into the hook underneath where we just defined the variables. That code should look like this inside of useDepartments.js
:
useEffect(() => {
const fetchDepartments = async () => {
const allDepartments = await departmentApi.getAllDepartments();
if (allDepartments === FETCH_DEPARTMENT_DATA_ERROR) {
setError(true);
} else {
setDepartments(allDepartments);
}
setLoading(false);
};
fetchDepartments();
}, []);
Okay, we're making good progress.
Remove setLoading
and return departments
and errors
from the hook#
The next thing we're going to need to do is remove the setLoading
state in this function. When you look at the two components calling the department API, both have a loading
variable that displays some sort of loading spinner until the data's been fetched and transformed.
Once the data's returned, the loader state is set to false
, which allows the component to render with the data it retrieves (or the error message it displays if it's failed).
Now, you may be thinking, "Why don't we just declare a loading
state variable inside of this hook as well?" We could. 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.
In this case, it makes more sense to remove the setLoading
variable from this hook and let the loading
state be handled in the components themselves.
This page is a preview of The newline Guide to Modernizing an Enterprise React App