Http Requests With React Hooks

Radovan Stevanovic
4 min readDec 8, 2019

By now we have all heard to so many times that components should not handle async actions as Http requests, that that should be abstracted from them in some higher layer. If you are using Redux you will probably use thunks or sagas, if you are using Context you will do the actual Http calls in that context provider layer.

https://i.udemycdn.com/course/750x422/2337614_baa8_5.jpg

That is all clear, I guess, for reference, I wrote about handling state with async actions and http calls with context while implementing redux like architecture before

Now let’s focus on the subject of this post, and that is how I think we can write these wrappers around http calls with React Hooks so we can have clean code and separation of concerns.

Why Hooks?
First of all, because they are awesome! Hooks allow you to hide internal implementation and just expose public API like modules essentially do, also, hooks have access to the Context, which we will find to be very powerful, also with hooks you achieve a structure that is easy to test and reason about.

Disclaimer: For the sake of this post we will use Axios, but same principles apply to Fetch or something else for handling the Http requests

Let’s create our generic useHttp hook.

import axios, { AxiosResponse, AxiosError } from 'axios'interface UseHttp {
get: <T>(url: string) => Promise<AxiosResponse<T>>;
post: <T>(url: string, payload: any) => Promise<AxiosResponse<T>>;
patch:<T>(url: string, payload: any) => Promise<AxiosResponse<T>>;
put: <T>(url: string, payload: any) => Promise<AxiosResponse<T>>;
remove: (url: string) => Promise<AxiosResponse<T>>;
}
export const useHttp = (): UseHttp => {
const formatUrl = (partialUrl: string): string => {
return `${process.env.REACT_APP_API_URL}/${partialUrl}`;
};
const get = async <T>(url: string): Promise<AxiosResponse<T>> => {
return axios.get(formatUrl(url));
};

const post = <T> (
url: string,
payload: any
): Promise<AxiosResponse<T>> => {
return axios.post(formatUrl(url), payload);
};
const patch = <T>(
url: string,
payload: any
): Promise<AxiosResponse<T>> => {
return axios.patch(formatUrl(url), payload);
};
const put = <T>(
url: string,
payload: any
): Promise<AxiosResponse<T>> => {
return axios.put(formatUrl(url), payload);
};
const remove = (url: string): Promise<AxiosResponse<T>> => {
return axios.delete(formatUrl(url));
};
return { get, post, patch, put, remove };
};

Looks good, but we should not stop here as I’ve said before if we wrap Http calls with hooks we get access to the context, and now I’ll illustrate why is that so awesome.

Let’s say that as usual, you have some API that requires some authentication, or you just need to send user with every request as a tenant, or something similar, now that we have access to the context we can easily use current user from the context and it to every request.

import axios, { AxiosResponse, AxiosError } from 'axios';
import { useAuthContext } from '...'
...export const useHttp = (): UseHttp => {
const { token } = useAuthContext();
const getHeaders = (): Headers => ({
headers: { Authorization: "Bearer " + token }
})
... const get = async <T>(url: string): Promise<AxiosResponse<T>> => {
return axios.get(formatUrl(url), getHeaders());
};

... // Same For All
};

Ok, let’s go a step further, we probably want to handle errors if they occur, if we imagine that we have some error context that is in charge of showing general errors to the user.

import axios, { AxiosResponse, AxiosError } from 'axios';
import { useAuthContext } from '...'
import { useErrorContext } from '...'
...export const useHttp = (): UseHttp => {
...
const { setError } = useErrorContext();

axios.interceptors.response.use(
res => {
return res;
},
(error: AxiosError) => {
if (!error.response) { return;}
setError(error.response.data);
}
);
...};

We can do so much more, but I think that you are getting a pretty clear picture of everything that is possible here. But we will not use this hook directly, you can if you want, but I would like to create specialization of this hook, so it can be used for a specific domain.

For example, imagine that we have some analytics API that we need to consume, we can now easily create specialization of useHttp hook.

useAnalyticsHttp.ts

import { AxiosResponse } from 'axios';
import { useHttp } from '...';
import { TerritoryAnalyticsModel, TopMovieModel } from '...';
import { ServiceBaseUrls } from '...';
export const useAnalyticsHttp = () => {
const { get } = useHttp();
const getTopTerritoriesAnalytics = async (
userId: string
): Promise<AxiosResponse<TerritoryAnalyticsModel[]>> => {
const { data } = await get(
`/analytics/top-territories?userId=${userId}`
);
return data ? data : [];
};
... return { getTopTerritoriesAnalytics, ... };
};

You can now use this new hook inside of your components or context provider layer, depending on your app architecture, but either way, you have now extracted logic from your component to this custom hooks, this will lead to much cleaner code and true separation of concerns.

Happy Codding!

--

--

Radovan Stevanovic

Curiosity in functional programming, cybersecurity, and blockchain drives me to create innovative solutions and continually improve my skills