Hello folks,
Frontend apps are not complete if there are no api calls involved and calling an api becomes a little repetitive thing to do. By creating a custom hook for this, we can save this repetition. To make an api call from frontend, popular methods are fetch and axios. Because of the feature like interceptors which axios support, we will be using them in this hook.
We will create useAxios hook in the following steps -
1- Do the api call from a component using axios.
2. Add states for the API response, loading and error.
3. Create a hook for calling an API using all above.
4. Make the hook dynamic, to call all types of API methods.
If you don’t want to go through these steps and directly jump to the final code, check here.
Now, let's dive in and create our custom hook, step by step!
1. Simple API call from the component
To create this example, we will be using jsonplaceholder’s posts api. They have many more such APIs created for the practice purpose.
Generally, all the apis of an app have the same base URL. We will first set up our base URL for axios, so will not need to pass it again and again. In case you are using more than one base URLs, axios supports it via creating instances. You can check that in their documentation.
In our App component, we will just call a get api to get the list of posts. For this, we are using useEffect hook. The basic api call from an App component will look something like this -
//App Component
import { useEffect } from 'react';
import axios from 'axios';
axios.defaults.baseURL = 'https://jsonplaceholder.typicode.com';
const App = () => {
const fetchData = () => {
axios
.get('/posts')
.then((res) => {
console.log(res);
})
.catch((err) => {
console.log(err);
});
};
useEffect(() => {
fetchData();
}, []);
return (
<div className='app'>
//do something
</div>
);
};
export default App;
The api call made above is simple. We used axios.get to call an api and using promises we will get the result or the error. Because we already had set up a baseURL, we just passed the specific path to the axios method.
2. Adding different states to the API call
But till now, we are just logging the response coming from api. Let’s use react’s states to save our response and error, if occurred. Also, we will be adding a loading state to conditionally show loaders on the page.
// App Component
import { useState, useEffect } from 'react';
import axios from 'axios';
axios.defaults.baseURL = 'https://jsonplaceholder.typicode.com';
const App = () => {
const [response, setResponse] = useState(null);
const [error, setError] = useState('');
const [loading, setloading] = useState(true);
const fetchData = () => {
axios
.get('/posts')
.then((res) => {
setResponse(res.data);
})
.catch((err) => {
setError(err);
})
.finally(() => {
setloading(false);
});
};
useEffect(() => {
fetchData();
}, []);
return (
<div className='app'>
//do something
</div>
);
};
export default App;
3. Creating a custom hook
Custom hooks might be overwhelming in the beginning. But, if you view them just like other components, they will make more sense. One thing to keep in mind, custom hooks are just another component, which returns values instead of JSX. This is my definition for custom hooks and somehow it made the concept more clear to me. You can read more about custom hooks here.
So now, let’s copy the logic of calling an api from our app component to our custom hook. So, the first draft of our useAxios will look something like this -
// useAxios hook (first draft)
import { useState, useEffect } from 'react';
import axios from 'axios';
axios.defaults.baseURL = 'https://jsonplaceholder.typicode.com';
const useAxios = () => {
const [response, setResponse] = useState(null);
const [error, setError] = useState('');
const [loading, setloading] = useState(true);
const fetchData = () => {
axios
.get('/posts')
.then((res) => {
setResponse(res.data);
})
.catch((err) => {
setError(err);
})
.finally(() => {
setloading(false);
});
};
useEffect(() => {
fetchData();
}, []);
// custom hook returns value
return { response, error, loading };
};
export default useAxios;
If you notice carefully, we have literally copy pasted the code and created a custom hook. The only difference is this hook is returning us 3 values, loading, response and error.
Till now, everything looks fine but the hook we created is not at all dynamic. If we need to change the API path or if we want to make a post call instead of get, then we are right now not capable of doing so.
Hence, here comes the last step of making our hook more flexible. -
4. Making our hook more dynamic
To make our hook dynamic, we can create a variable for the url path and pass it as a prop to our hook. Also, axios can have any method from get, put, post and delete. Hence, we will need a variable for method name too. With path and methods, we will be adding two variables which can be used to pass body and headers to the request. After adding all these, our hook will look something like this -
Final code
// useAxios hook
import { useState, useEffect } from 'react';
import axios from 'axios';
axios.defaults.baseURL = 'https://jsonplaceholder.typicode.com';
const useAxios = ({ url, method, body = null, headers = null }) => {
const [response, setResponse] = useState(null);
const [error, setError] = useState('');
const [loading, setloading] = useState(true);
const fetchData = () => {
axios[method](url, JSON.parse(headers), JSON.parse(body))
.then((res) => {
setResponse(res.data);
})
.catch((err) => {
setError(err);
})
.finally(() => {
setloading(false);
});
};
useEffect(() => {
fetchData();
}, [method, url, body, headers]);
return { response, error, loading };
};
export default useAxios;
As our useAxios hook is ready, let’s now use it into our app component and try to create a new post using that. Hence, the App component will be -
// App Component
const App = () => {
const { response, loading, error } = useAxios({
method: 'post',
url: '/posts',
headers: JSON.stringify({ accept: '*/*' }),
body: JSON.stringify({
userId: 1,
id: 19392,
title: 'title',
body: 'Sample text',
}),
});
const [data, setData] = useState([]);
useEffect(() => {
if (response !== null) {
setData(response);
}
}, [response]);
return (
<div className='App'>
<h1>Posts</h1>
{loading ? (
<p>loading...</p>
) : (
<div>
{error && (
<div>
<p>{error.message}</p>
</div>
)}
<div>{data && <p>{data.id}</p>}</div>
</div>
)}
</div>
);
};
export default App;
This is the very basic version of useAxios hook. You can add more customisations to it as per your requirements.
Thank you so much for reading this article and do let me know your thoughts about this custom hook! Also, for daily updates you can also connect with me on Twitter or buy me a coffee if you like my articles.
Keep learning 🙌
Latest comments (62)
Just a quick question the useAxios is declared in the outer so want it run on every render of the app, making it go in an infinite loop
It's great, but do you have any idea to add request cancellation feature to this hook?
Unfortunately, I have to admit that this custom hook is futile and impractical. Could you please give a real-life example instead of making a request manually like
useAxios({ data: "HAHA look" })?Like @anastawfik mentioned, it's not possible to call this hook inside a JavaScript function, i.e. the submit handler, which is like 90% of cases where we would need such a hook in the first place.
In any case, it's more to do with React.
A little modification and you can use it on click.
Read here
I tried to use this code snippets and got cors error.
How to fix it?
I've used this solution with TypeScript and added an option to cancel the request:
Thank you it helped me a lot
I made a get call to fetch the posts using get method, but I want to make a post reuest when user clicks on Add button , so I am calling the hook as ->
this is causing error , how can I make a post request then.
codesandbox.io/s/currying-bush-yic...
@ms_yogii how would you write unit tests for this custom hook?
Nice hook!
In component:
what if I want to submit something... kindda confusing!
Some comments have been hidden by the post's author - find out more