How to create a custom React hook to fetch an API (using TypeScript)?
Hooks are convenient for modern react development. The react framework comes with standard hooks to manage state, for example, with useState, and here we will write our hook to fetch data from any API.
Buț first …
… what is a hook?
A hook is a javascript or typescript function that can include other hooks. Its name starts with « use », and this function can only be called inside a React functional component.
You can find the complete Rules of Hooks documentation here.
Let’s start
First, create a new React project using Typescript.
In the terminal, navigate to the desired folder, and with the terminal command :
npx create-react-app apihook --template typescript
The project is ready, time to think about the output of our hook to set the goal.
The output
Our hook will fetch an API, and the function will return a result.
For this example, we want to get the following information:
- response status code: to test the response code
- response status text: to get the response status in a more readable way
- data: data provided by the API
- error: description of the error if one occurs
- loading: to know if the process is running
We will write a type to set that!
Coding!
I will create a new folder to store my hook and a new file named useApiHook.ts
And set my type as following :
export type TApiResponse = {
status: Number;
statusText: String;
data: any;
error: any;
loading: Boolean;
};
We will now declare my hook as a function that will take a string containing the url as parameter and return a TApiResponse :
export type TApiResponse = {
status: Number;
statusText: String;
data: any;
error: any;
loading: Boolean;
};
export const useApiGet = (url: string): TApiResponse => {};
We will also use the state to store the information before returning the response. For this purpose, we will use a standard hook named useState, and import this function from the React framework :
import { useState } from 'react';
export type TApiResponse = {
status: Number;
statusText: String;
data: any;
error: any;
loading: Boolean;
};
export const useApiGet = (url: string): TApiResponse => {
const [status, setStatus] = useState<Number>(0);
const [statusText, setStatusText] = useState<String>('');
const [data, setData] = useState<any>();
const [error, setError] = useState<any>();
const [loading, setLoading] = useState<boolean>(false);
};
Please note that we initialize status and textStatus to avoid « undefined ». If not, we would get a TypeScript error telling that it doesn’t match the type we defined (the power of TypeScript !).
Time to get the data!
Here we will use an async function to create a promise and get the data. We will also use try/catch to catch an error if something wrong happens.
We also set isLoading to ‘true’, so the process will be set as running :
import { useState } from 'react';
export type TApiResponse = {
status: Number;
statusText: String;
data: any;
error: any;
loading: Boolean;
};
export const useApiGet = (url: string): TApiResponse => {
const [status, setStatus] = useState<Number>(0);
const [statusText, setStatusText] = useState<String>('');
const [data, setData] = useState<any>();
const [error, setError] = useState<any>();
const [loading, setLoading] = useState<boolean>(false);
const getAPIData = async () => {
setLoading(true);
try {
const apiResponse = await fetch(url);
const json = await apiResponse.json();
} catch (error) {
}
};
};
We are almost done !
Now let’s store the results in the different states, and at the end, set isLoading to false to declare that the process is finished:
import { useState } from 'react';
export type TApiResponse = {
status: Number;
statusText: String;
data: any;
error: any;
loading: Boolean;
};
export const useApiGet = (url: string): TApiResponse => {
const [status, setStatus] = useState<Number>(0);
const [statusText, setStatusText] = useState<String>('');
const [data, setData] = useState<any>();
const [error, setError] = useState<any>();
const [loading, setLoading] = useState<boolean>(false);
const getAPIData = async () => {
setLoading(true);
try {
const apiResponse = await fetch(url);
const json = await apiResponse.json();
setStatus(apiResponse.status);
setStatusText(apiResponse.statusText);
setData(json);
} catch (error) {
setError(error);
}
setLoading(false);
};
};
To finish our custom hook, we need to trigger the function we have crated. To do so, we use another standard hook : useEffect().
This hook will execute code when the component loads or some variable has changed.
We will only use it when the component is loaded for our purpose.
We need first to import it and use it to call our function :
import { useState, useEffect } from 'react';
export type TApiResponse = {
status: Number;
statusText: String;
data: any;
error: any;
loading: Boolean;
};
export const useApiGet = (url: string): TApiResponse => {
const [status, setStatus] = useState<Number>(0);
const [statusText, setStatusText] = useState<String>('');
const [data, setData] = useState<any>();
const [error, setError] = useState<any>();
const [loading, setLoading] = useState<boolean>(false);
const getAPIData = async () => {
setLoading(true);
try {
const apiResponse = await fetch(url);
const json = await apiResponse.json();
setStatus(apiResponse.status);
setStatusText(apiResponse.statusText);
setData(json);
} catch (error) {
setError(error);
}
setLoading(false);
};
useEffect(() => {
getAPIData();
}, []);
return { status, statusText, data, error, loading };
};
Now that our hook is done let’s call it in the main application.
Use the custom hook
In our example, we will call the hook to fetch a movie database API and console.log the result.
We need to create an account on omdbapi.com to get a free API key required to pull the data.
In the file App.tsx, we will :
- import the type and the custom hook
- add the call to the API and store the result in a variable called
data
Then to display the result, I will use the property loading
from the response to avoid multiple print during the process:
import React from 'react';
import logo from './logo.svg';
import './App.css';
import { useApiGet, TApiResponse } from './hooks/useApiHook';
function App() {
// call to the hook
const data: TApiResponse = useApiGet(
'http://www.omdbapi.com/?s=Guardians&apikey=xxxxxxxx'
);
// print the output
if (!data.loading) console.log(data);
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.tsx</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer">
Learn React
</a>
</header>
</div>
);
}
export default App;
Run the app
Finally let’s run the app by typing in the console :
npm start
And …
🥳
Conclusion
Hooks can be super handy and allow the creation of reusable functions. They have to follow some rules to build them and are very flexible.
For our example, we could go further and extend the function to handle parameters, other methods, some checks and controls, but I wanted to keep it simple to explain the principle.
Now I invite you to create custom hooks for your react apps, and feel free to share some usages in the comments.
Happy coding!
Article also available on Medium
Top comments (5)
what is the use of coding with ts to put any at data, precisely I would like to know what to put in place of any so that it works in strict mode
@elyas This hook is meant to be used by reused by various components or functions. By design, it can accept any url as an argument and return any data that gets returned from that API. If you know what kind of data you are expecting to get back, you can make an interface specific for that component.
In this example we know we are expecting to get back movie info and we can adjust the App component like this:
The same thing can be done for any API returning "any" kind of data.
setData not dataSet
@sulistef Great article. Thanks for sharing!
I want to fetch data every time a value changes but i can't call this hook inside useEffect(), what should i do?