In this article, I will explain how to create a custom hook to make API calls in a React Js application. Creating a custom hook can significantly reduce the boilerplate code throughout my application.
Here is the complete code for the custom hook that can be used to call API endpoints. I use https://jsonplaceholder.typicode.com/ as my test endpoint.
import axios, { AxiosError } from 'axios';
import { useState } from "react";
interface AxiosRequestConfigProps {
method: string
url: string
}
export interface TitleSearchResponse {
userId: number,
id: number,
title: string,
completed: boolean,
}
export type ResponseType = TitleSearchResponse
type HookReturnArray = [
(params: AxiosRequestConfigProps) => void,
ResponseType | null,
boolean,
string | null
]
export const useApiFetch = (): HookReturnArray => {
const [data, setData] = useState<ResponseType | null>(null);
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState<boolean>(false);
const fetchApiData = async (params: AxiosRequestConfigProps): Promise<void> => {
setLoading(true)
setError("")
axios.request(params)
.then((response) => {
setData(response.data)
})
.catch((err: Error | AxiosError) => { alert(err.message)
if (axios.isAxiosError(err)) {
setError(err.message)
} else {
setError("Something went wrong")
}
}).finally(() => {
setLoading(false)
})
}
return [fetchApiData, data, loading, error]
}
The interface
The interface
The hook returns an array that contains a function reference and variables. The function name allows me to call an API, and the variables deliver the response and related processing statuses.
Here is how to use the API hook.
import { useApiFetch, UserSearchResponse } from "./use_fetch_api";
const App = () => {
const [fetchApiData, data, loading, error] = useApiFetch()
const loadTitles = () => {
fetchApiData({
method: "get",
url: "https://jsonplaceholder.typicode.com/todos/1",
})
}
return (
<div>
<button onClick={() => loadTitles()}>Load data</button>
{loading && "Loading"}
{error && error}
{data &&
<div>
<p>{data.id}</p>
<p>{data.userId}</p>
<p>{data.title}</p>
<p>{data.completed ? "completed" : "not completed"}</p>
</div>
}
</div>
);
}
export default App;
So far, my API hook can call APIs that responses match the
Here is how I implement an API that returns an array of objects. The
Here is the complete code.
import axios, { AxiosError } from 'axios';
import { useState } from "react";
interface AxiosRequestConfigProps {
method: string
url: string
}
export interface TitleSearchResponse {
userId: number,
id: number,
title: string,
completed: boolean,
}
export interface UserSearchResponse {
id: number,
name: string,
username: string,
email: string,
address: {
street: string,
suite: string,
city: string,
zipcode: string,
geo: {
lat: string,
lng: string
}
}
}
export type ResponseType = TitleSearchResponse & UserSearchResponse[]
type HookReturnArray = [
(params: AxiosRequestConfigProps) => void,
ResponseType | null,
boolean,
string | null
]
export const useApiFetch = (): HookReturnArray => {
const [data, setData] = useState<ResponseType | null>(null);
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState<boolean>(false);
const fetchApiData = async (params: AxiosRequestConfigProps): Promise<void> => {
setLoading(true)
setError("")
axios.request(params)
.then((response) => {
setData(response.data)
})
.catch((err: Error | AxiosError) => {
if (axios.isAxiosError(err)) {
setError(err.message)
} else {
setError("Something went wrong")
}
}).finally(() => {
setLoading(false)
})
}
return [fetchApiData, data, loading, error]
}
Here is how I can call the custom hook that can handle multiple API response types.
import { useApiFetch, UserSearchResponse } from "./use_fetch_api";
const App = () => {
const [fetchApiData, data, loading, error] = useApiFetch()
const [fetchApiDataUsers, userData, userLoading, userError] = useApiFetch()
const loadTitles = () => {
fetchApiData({
method: "get",
url: "https://jsonplaceholder.typicode.com/todos/1",
})
}
const loadUsers = () => {
fetchApiDataUsers({
method: "get",
url: "https://jsonplaceholder.typicode.com/users",
})
}
return (
<div>
<button onClick={() => loadTitles()}>Load data</button>
{loading && "Loading"}
{error && error}
{data &&
<div>
<p>{data.id}</p>
<p>{data.userId}</p>
<p>{data.title}</p>
<p>{data.completed ? "completed" : "not completed"}</p>
</div>
}
<button onClick={() => loadUsers()}>Load data</button>
{userLoading && "Loading"}
{userError && error}
{userData?.map((user: UserSearchResponse) => {
return <div>
<p>{user.name}</p>
<p>{user.email}</p>
<p>{user.address.city}</p>
</div>
})}
</div>
);
}
export default App;