Writing a custom Axios hook in TypeScript for API calls in your React application

Writing a custom Axios hook in TypeScript for API calls in your React application

ยท

5 min read

Axios is a heavily used HTTP client for making API calls in the browser and through Node.js. (By heavily used, I mean the npm package has 17M+ weekly downloads!) Today we'll explore how to write a custom Axios hook in TypeScript, for making API calls in our React application. Custom hooks increase reusability and make our components more lightweight, so this is definitely worth trying if you are using Axios in your project!

Structure of the hook

The basic structure of the hook contains the following:-

  • The state
  • Function to interact with the API
  • Return statement

Our custom axios hook will have 3 states, response, error and loading. Let's first start with that.

  const [response, setResponse] = useState<AxiosResponse>();
  const [error, setError] = useState<AxiosError>();
  const [loading, setLoading] = useState(true);

Now, let's write the function for interacting with the API. The function will accept an Axios config object as a parameter and return the response or error in case of a successful or a failed HTTP request respectively.

const fetchData = async (params: AxiosRequestConfig) => {
    try {
      const result = await axios.request(params);
      setResponse(result);
    } catch( err ) {
      setError(err);
    } finally {
      setLoading(false);
    }
  };

Now we want this function to be called once when we load the component where we use this hook. So, we'll make use of useEffect for that.

useEffect(() => {
  fetchData(axiosParams);
},[]);

Finally, our hook will accept and AxiosConfig object as a param and return the 3 states - response, error, and loading. This is how the complete hook would look -

import { useState, useEffect } from 'react';
import axios, { AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios';

axios.defaults.baseURL = 'https://jsonplaceholder.typicode.com';

const useAxios = (axiosParams: AxiosRequestConfig) => {
  const [response, setResponse] = useState<AxiosResponse>();
  const [error, setError] = useState<AxiosError>();
  const [loading, setLoading] = useState(true);

 const fetchData = async (params: AxiosRequestConfig) => {
    try {
      const result = await axios.request(params);
      setResponse(result);
    } catch( err ) {
      setError(err);
    } finally {
      setLoading(false);
    }
 };

useEffect(() => {
  fetchData(axiosParams);
},[]);

 return { response, error, loading };
}

Using our custom hook inside a component

Now, our hook is ready to be used inside any React component. Let's try it out! We'll try to load a single post in our component using this hook from JSON placeholder. (provides a bunch of mock APIs)

function App() {
  const { response, loading, error, sendData } = useAxios({
    method: "GET",
    url: `/posts/1`,
    headers: {
      accept: '*/*'
    }
  });

  return (
    <div className="App">
      <h1 className="page-title">Posts</h1>
      {loading && (
        <p>Loading...</p>
      )}
      {error && (
        <p>{error.message}</p>
      )}
      {!loading && !error && (
        <article className="post">
          <h3 className="post-title">{response?.data.title}</h3>
          <p className="post-body">
            {response?.data.body}
          </p>
        </article>
      )}
    </div>
  );
}

Now, our custom hook works perfectly fine for this scenario. Let's now look into how we can refine our hook even more and make it work with even more use cases.

Refining our hook

Now, our useAxios hook works fine for components where we are making an API request on loading the component. But, what about scenarios where we want to make a call on the click of some button or POST/PATCH requests? We need to refine our hook somewhat for working with those scenarios.

Let's assume that when we're fetching some data, or making a GET request, we want to always fetch it on loading the component. So our current code would work fine in this case. You can modify this according to your use case of course. Now, we can check the method of the request from the params passed on to our hook. We need to modify two things -

  • The loading state
const [loading, setLoading] = useState(axiosParams.method === "GET" || axiosParams.method === "get");
  • useEffect (where we're calling the function)
useEffect(() => {
  if(axiosParams.method === "GET" || axiosParams.method === "get"){
    fetchData(axiosParams);
  }
},[]);

Now, we need to add another method that will trigger the Axios call manually. We also need to add that function to the return statement of useAxios so that we can access it in other components.

const sendData = () => {
  fetchData(axiosParams);
}

return { response, error, loading, sendData };
/* replace the previous return statement with this */

Finally, our refined hook is ready! Let's see how it works inside a component now. This time we'll make it more dynamic, and try to display the next post with every click.

function App() {
  const [postId, setPostId] = useState(1);
  const { response, loading, error, sendData } = useAxios({
    method: "get",
    url: `/posts/${postId}`,
    headers: {
      accept: '*/*'
    }
  });

  const getNextPost = () => {
    setPostId(postId + 1);
    sendData();
  }
  return (
    <div className="App">
      <h1 className="page-title">Posts</h1>
      {loading && (
        <p>Loading...</p>
      )}
      {error && (
        <p>{error.message}</p>
      )}
      {!loading && !error && (
        <article className="post">
          <h3 className="post-title">{response?.data.title}</h3>
          <p className="post-body">
            {response?.data.body}
          </p>
        </article>
      )}
      <button onClick={() => getNextPost()}>
        Next Article Please!
      </button>
    </div>
  );
}

Now we'll see that a new post gets loaded every time we hit the 'Next Article Please' button, meaning we are sending the API call dynamically on button click!

You can play around with it more and make it even more suited for the use case of your project! I took the first reference of writing this hook from here.

TL: DR

Here's the complete code for the custom hook if you are here just for the code ๐Ÿ˜‰

import { useState, useEffect } from 'react';
import axios, { AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios';

axios.defaults.baseURL = 'https://jsonplaceholder.typicode.com';
//If you are using different URLs, consider removing this line and adding a baseURL in the Axios Config parameter. 

const useAxios = (axiosParams: AxiosRequestConfig) => {
  const [response, setResponse] = useState<AxiosResponse>();
  const [error, setError] = useState<AxiosError>();
  const [loading, setLoading] = useState(axiosParams.method === "GET" || axiosParams.method === "get");

  const fetchData = async (params: AxiosRequestConfig) => {
    try {
      const result = await axios.request(params);
      setResponse(result);
    } catch( err ) {
      setError(err);
    } finally {
      setLoading(false);
    }
  };

  const sendData = () => {
    fetchData(axiosParams);
  }

  useEffect(() => {
    if(axiosParams.method === "GET" || axiosParams.method === "get"){
      fetchData(axiosParams);
    }
  },[]);

  return { response, error, loading, sendData };
}

export default useAxios;

The code for a sample Component where we use this hook is just above!

Hope you enjoyed writing down the custom Axios hook with me, see you in the next one! ๐Ÿ‘‹

Did you find this article valuable?

Support Sreejit De by becoming a sponsor. Any amount is appreciated!