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!
Create Your Own Hooks to Simplify Code
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.
Create a Hook for Your API Calls
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 } = data
You 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 } = data
surveyData
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! 🎉
Add Error Handling
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.
Handle Hooks With Confidence
Stick to the Rules
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:
useRef
Read the documentation if you're interested. Although the useRef
hook has several uses, it’s mainly used for interacting with DOM elements.
useReducer
useReducer
lets you better manage your state when it includes lots of properties that require periodic modification.
useMemo and useCallback
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.
Give It a Go!
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.
Let’s Recap!
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! 💪