If you took the first course, you learned about hooks through useState and useEffect . In this course, you saw how to use these hooks to make API calls. And in the last chapter, you discovered context and how to easily access it with useContext . You now have quite a few tricks up your sleeve when it comes to using hooks in your React applications. 🎉
Ready to create your own hooks? You can do it!
I don’t know about you, but the first time someone told me I could make my own hooks, I panicked slightly. I imagined working on the React codebase and creating an enormous file to run a custom hook.
Don't worry; it’s easy to create a custom hook: it’s simply a function that starts with “use,” which extracts the reusable logic and can use other hooks.
Let’s see how this works in practice.
In chapter 1, you called the API for the Shiny project to get freelancer profiles and the questions for a survey.
Take a closer look at your code. Can you see some repetition? That’s totally normal – we’re going to try to pool all of that with a custom hook!
To do this, create a new file in /utils and call it /hooks . Then create an index.jsx file.
For creating the hook, use the syntax async / await :
import { useState, useEffect } from 'react'
export function useFetch(url) {
const [data, setData] = useState({})
const [isLoading, setLoading] = useState(true)
useEffect(() => {
if (!url) return
async function fetchData() {
const response = await fetch(url)
const data = await response.json()
setData(data)
setLoading(false)
}
setLoading(true)
fetchData()
}, [url])
return { isLoading, data }
}The code’s quite clear, isn’t it? For the new useFetch hook, pass the URL of the API you want to call as a parameter. It has an internal state that lets it store data and know if data is loading with isLoading .
In useEffect , the hook carries out an empty return if the URL parameter is empty, and starts by setting isLoading to true . It declares the asynchronous function fetchData in order to:
Call fetch .
Parse what is returned with data.json() .
Change the state of isLoading .
url is part of the useEffect list of dependencies, which triggers the call if the URL passed as a parameter change. Then, call the function fetchData .
To use the new hook, modify /Survey/index.jsx . Start by importing your hook with:
import { useFetch } from '../utils/hooks'Then get the data with:
const { data, isLoading } = useFetch(`http://localhost:8000/survey`)
const { surveyData } = dataYou should also delete all of the content in useEffect that would have performed the fetch . Deleting unnecessary code is incredibly satisfying, don’t you think? 😍
Don’t forget to replace isDataLoading with isLoading , which will be more generic here, and replace surveyData with data .
Does it still work?
Uh oh. There’s an error that is stopping the code from running! 😭
It’s nothing to worry about! With navigation, we accessed the content of the question object with data[questionNumber] . Except during set up, data is an empty object, which means that with:
const { surveyData } = datasurveyData is undefined. Therefore, JavaScript generates an error for data[questionNumber] . One way of avoiding the error is to check that surveyData is defined before using it in the component:
<QuestionContent>
{surveyData && surveyData[questionNumber]}
</QuestionContent>The page once again works like before using the useFetch hook, which means a lot of repetitive code was deleted! 🎉
But what happens when the API returns an error? The app won’t behave the way you want it to, and your user won’t have any idea what’s going on. It’s a good idea to integrate error handling in the useFetch hook so that any problem appears on the screen.
To do this, create a state for error in utils/hooks/index.jsx with:
const [error, setError] = useState(false)Then add a try and a catch to the useFetch hook:
export function useFetch(url) {
const [data, setData] = useState({})
const [isLoading, setLoading] = useState(true)
const [error, setError] = useState(false)
useEffect(() => {
if (!url) return
setLoading(true)
async function fetchData() {
try {
const response = await fetch(url)
const data = await response.json()
setData(data)
} catch (err) {
console.log(gerr)
setError(true)
} finally {
setLoading(false)
}
}
fetchData()
}, [url])
return { isLoading, data, error }
}That lets you pass error to true whenever you run into a problem.
Similarly, in Survey.jsx , you now get error:
const { data, isLoading, error } = useFetch(`http://localhost:8000/survey`)And you can just add the following above the return:
if (error) {
return <span>There is a problem</span>
}Brilliant! You’re even able to handle API call errors! 🎉
With this knowledge in hand, use useFetch to send user responses and get the API results from the /results page.
Watch how to do all of this in the screencast below. 👇
Will I be able to use useFetch in all of my codebases in production?
This useFetch hook is excellent for avoiding repetition and practicing how to create custom hooks. However, in reality, this code is a little too basic to be used in production. Instead, you could use a much more robust tool such as React Query, which will let you use hooks to make queries and cache them in your applications.
A quick reminder: hooks have their own set of rules.
Hooks are only accessible in a React function component, meaning that it is not possible to use them in a class component or a simple JavaScript function.
Call hooks at the level of your component root.
Be careful when naming your custom hooks. Even if this is a convention rather than a strict rule, your custom hooks should start with use so you can immediately identify them.
For now, here are a few hooks that you’re likely to come across in different codebases:
Read the documentation if you're interested. Although the useRef hook has several uses, it’s mainly used for interacting with DOM elements.
useReducer lets you better manage your state when it includes lots of properties that require periodic modification.
These two hooks allow you to avoid redoing calculations that might impact performance. You can specify the values for which you need to redo calculations only if one of the parameters changes, thanks to useMemo and useCallback .
And there are more! React is currently on version 17 (at the time this course was written). However, they could have created newer hooks in the meantime.

Now that you’ve created your custom hook for making API calls, useFetch , you can use it for the /Freelancers and /Results pages.
You’ll also have to create a personalized useTheme hook that gets the context from the theme and lets you get the current theme ( dark or light ).
From there, you can integrate the style changes for the theme and then integrate the /results page as per the prototypes if you want to challenge yourself (or get the CSS from the solution branch if you’d prefer to spend less time on the CSS).
As usual, you’ll find the exercise on branch P2C3-begin and the solution on P2C3-solution.
A custom hook is a function that starts with use , extracts the reusable logic, and can use other hooks.
The rules of hooks also apply to custom hooks, as does the convention of naming your custom hook use ... .
Other hooks exist such as useRef , useReducer , useMemo , etc.
Your app is starting to look pretty good! I hope that you’ve enjoyed taking a closer look at hooks. But how can you be sure that you’re not going to break everything if you make changes in 6 months, or even a year, when you no longer remember how your code is implemented? Through tests, of course, which is the topic of the next part. But before moving on, it’s time to test your knowledge with a quiz. Best of luck, and see you in a bit! 💪