The idea is to create 4 different components, and each component will make a type of request (GET, POST, PUT, DELETE).
Beforehand I mention that there will be repeated code. If you wish you can refactor, but I will not do it because my purpose is to teach you how to use axios!
We create the folder src/components and inside we create 4 files:
CreatePost.tsx
GetPost.tsx
UpdatePost.tsx
DeletePost.tsx
🚨 Note: Each time we create a new folder, we will also create an index.ts file to group and export all the functions and components of other files that are inside the same folder, so that these functions can be imported through a single reference, this is known as barrel file.
🕯️ What is axios?.
Axios is a promise-based HTTP client library that can be used in both Node JS and the browser, so we can configure and make requests to a server and receive easy-to-process responses.
It helps us in sending asynchronous HTTP requests, thus helping us to perform CRUD operations.
It is also a good alternative to the default Fetch API of browsers.
🕯️ Creating a new instance of axios.
First we install axios:
npm install axios
Let's create the src/api folder and create the client.ts file.
Inside the file, we are going to import axios and we are going to create a new instance of axios (most of the time you will always want to do this).
This helps us to have a centralized configuration in one place and customized, since most of the time we don't want to go around putting the same configuration every time we make a request.
The create property of axios receives a configuration object, where in this case we pass the baseURL property, which is the base url to which we will make the requests.
You can also set the headers, the timeout (number that indicates the time in milliseconds for the request to be aborted), and other properties, but only the base URL is enough for this example.
We are going to use the JSONPlaceholder API to make the requests.
And at once we define, right here, the interface of the response that the API is going to give us.
First we are going to src/App.tsx and we are going to import all the components that we created previously.
We are going to place first the GetPost and then we will comment it to place the next one and so on.
Notice that we execute the get function and inside we put some empty quotes, why?
Well this is necessary to let the instance know that we don't want to add more parameters to our base URL.
Because at the moment we have the URL like this: "https://jsonplaceholder.typicode.com/posts", so whatever we add inside the get method (or any other method) will be concatenated to this URL.
That's why we only put single and empty quotes referring to the fact that we don't want to concatenate anything.
But if we want to concatenate something like for example the limit of results of the API we will place it in the following way:
Now, this promise returns an object that contains several properties such as the configuration, the headers, the state of the request, among others, but the one that interests us is the data property. The data property is what we are going to return, because that is where the API response will come from.
Note that the get method is also typed with ResponseAPI[], just in case, and even if you don't type it and return the data, it will work because the default data property is of type any.
import{client,ResponseAPI}from"../api"exportconstgetPosts=async ():Promise<ResponseAPI[]>=>{const{data}=awaitclient.get<ResponseAPI[]>('?_limit=6')
return data
}
That's how easy it is to make a GET request.
Now returned to src/components/GetPost.tsx.
We implement an effect to make the API call (in this occasion we will do it like this, although it is recommended to use a library to handle the cache of the requests like React Query).
Inside the effect we execute the function getPosts and we solve the promise to later establish the data that returns us this promise.
import{useState,useEffect}from'react';import{ResponseAPI}from'../api';import{getPosts}from'../utils';exportconstGetPost=()=>{const[posts,setPosts]=useState<ResponseAPI[]>([])useEffect(()=>{// You can implement a <Loading/>// start loadinggetPosts().then(data=>setPosts(data))// finish loading},[])return (<><h1>Get Post 👇</h1><br/><h2>posts list</h2><divclassName='grid'>{posts.map(post=>(<divkey={post.id}><p>Title: <span>{post.title}</span></p><p>Body: <span>{post.body}</span></p><p>User: <span>{post.userId}</span></p></div>))}</div></>)}
And this is what the rendered API data would look like
🕯️ Creating POST request.
Now we go to the src/components/CreatePost.tsx file and create a new component similar to the previous one.
This component renders the same list of posts and stores them in a state as well.
Note that the key, at the moment of going through the state of posts, is post.userId because this is the only value that is different when we create a new post.
Also note that a button was added, to create post, we will do it without a form, but the best thing would be to receive the values by a form.
This button, in its onClick event executes the handleClick function, which at the moment does nothing, but that is where we have to execute the method to create a new post.
import{useState}from'react';import{ResponseAPI}from'../api';exportconstCreatePost=()=>{const[posts,setPosts]=useState<ResponseAPI[]>([])consthandleClick=async ()=>{}return (<div><h1>Create Post 👇</h1><buttononClick={handleClick}>Add Post</button><divclassName='grid'>{posts.map(post=>(<divkey={post.userId}><p>Title: <span>{post.title}</span></p><p>Body: <span>{post.body}</span></p><p>User: <span>{post.userId}</span></p></div>))}</div></div>)}
Next, let's create a new file in src/utils named createPost.ts.
Inside the file we create a new function that will return a promise resolving a single ResponseAPI. And also, this function receives some parameters that are necessary to create a new post.
But the post method not only receives the URL, but also needs the body or the data to send to create new information.
So that body, we pass it as a second parameter, in an object.
Ready, we have our function to create a new post, now we are going to use it in src/components/CreatePost.tsx.
In the handleClick function we call the createPost function and pass it the necessary parameters. We use async/await to resolve the promise returned by the createPost function, which if everything is correct, should return a new post.
This new post, we are going to add it to the state, keeping the previous posts.
import{useState}from'react';import{ResponseAPI}from'../api';import{createPost}from'../utils';exportconstCreatePost=()=>{const[posts,setPosts]=useState<ResponseAPI[]>([])consthandleClick=async ()=>{constnewPost=awaitcreatePost("new title","something",Date.now())setPosts(prev=>([newPost,...prev]))}return (<div><h1>Create Post 👇</h1><buttononClick={handleClick}>Add Post</button><divclassName='grid'>{posts.map(post=>(<divkey={post.userId}><p>Title: <span>{post.title}</span></p><p>Body: <span>{post.body}</span></p><p>User: <span>{post.userId}</span></p></div>))}</div></div>)}
We probably won't see anything, but remember to comment out the old component and place the new one, in src/App.tsx.
Then we go to src/components/UpdatePost.tsx to create a new functional component that is the same as GetPost.tsx. since we need a list of existing posts in order to update any of them.
Note that the div that is rendered when scrolling through the posts:
Has a className='card'.
It has an onClick event that executes the handleUpdate function and we send it the id of the post as a parameter.
The function handleUpdate is asynchronous and receives the id of the post that is of type number, and at the moment it does not execute anything.
import{useState,useEffect}from'react';import{ResponseAPI}from'../api';import{getPosts}from'../utils';exportconstUpdatePost=()=>{const[posts,setPosts]=useState<ResponseAPI[]>([])useEffect(()=>{getPosts().then(data=>setPosts(data))},[])consthandleUpdate=async (id:number)=>{}return (<div><h1>Update Post 👇</h1><br/><h2>Click a card</h2><divclassName='grid'>{posts.map(post=>(<divclassName='card'key={post.id}onClick={()=>handleUpdate(post.id)}><p>Title: <span>{post.title}</span></p><p>Body: <span>{post.body}</span></p><p>User: <span>{post.userId}</span></p></div>))}</div></div>)}
Then, we go to src/utils and create a new updatePost.ts file.
Where it is basically almost the same as the post method.
Note that now the function parameters are defined in an interface.
The only difference between the post and the put is that in the URL if we have to place a new parameter that is the id of the post that we want to modify.
Now we are going to use our function, in src/components/UpdatePost.tsx.
In the handleUpdate function we call the updatePost function and pass it the necessary parameters, again we use async/await to resolve the promise and get the updated post.
Finally we set a new state, placing the updated post.
import{useState,useEffect}from'react';import{ResponseAPI}from'../api';import{getPosts,updatePost}from'../utils';exportconstUpdatePost=()=>{const[posts,setPosts]=useState<ResponseAPI[]>([])useEffect(()=>{getPosts().then(data=>setPosts(data))},[])consthandleUpdate=async (id:number)=>{constbody=`Body updated`consttitle=`Title updated`constuserId=Date.now()constpostUpdated=awaitupdatePost({id,body,title,userId})setPosts(prev=>([postUpdated,...prev.filter(post=>post.id!==id),]))}return (<div><h1>Update Post 👇</h1><br/><h2>Click a card</h2><divclassName='grid'>{posts.map(post=>(<divclassName='card'key={post.id}onClick={()=>handleUpdate(post.id)}><p>Title: <span>{post.title}</span></p><p>Body: <span>{post.body}</span></p><p>User: <span>{post.userId}</span></p></div>))}</div></div>)}
Then we go to src/components/DeletePost.tsx to create a new functional component that is the same as UpdatePost.tsx. since we need a list of existing posts to be able to delete some.
And now we have the handleDelete function that receives an id and at the moment does nothing.
Note that now the onClick event in each post, executes a function called handleDelete and the id of the post is passed to it.
import{getPosts}from"../utils"import{useEffect,useState}from'react';import{ResponseAPI}from"../api";exportconstDeletePost=()=>{const[posts,setPosts]=useState<ResponseAPI[]>([])useEffect(()=>{getPosts().then(data=>setPosts(data))},[])consthandleDelete=async (id:number)=>{}return (<><h1>Delete Post 👇</h1><br/><h2>Click a card</h2><divclassName="grid">{posts.map(post=>(<divclassName="card"key={post.id}onClick={()=>handleDelete(post.id)}><p>ID: <span>{post.id}</span></p><p>Title: <span>{post.title}</span></p><p>Body: <span>{post.body}</span></p></div>))}</div></>)}
Then we need to create a new src/utils file named deletePost.ts that inside will have an asynchronous function that will just return a promise resolving a boolean value to indicate if the post deletion was successful.
We just call the delete method of our instance and add the ID of the post we are going to delete.
In that case we have the same properties as when we execute a get, post or put. But the data is useful to us because we would get an empty object, in this case we will evaluate the status code of the request, if it is a status 200 means that everything went well then it would return a true.
Now we are going to use this function in src/components/DeletePost.tsx.
Inside the handleDelete function we execute the deletePost function sending the ID of the post we want to delete, and using async/await we resolve the promise to obtain the boolean value and based on that value, update the state.
import{deletePost,getPosts}from"../utils"import{useEffect,useState}from'react';import{ResponseAPI}from"../api";exportconstDeletePost=()=>{const[posts,setPosts]=useState<ResponseAPI[]>([])useEffect(()=>{getPosts().then(data=>setPosts(data))},[])consthandleDelete=async (id:number)=>{constisSuccess=awaitdeletePost(id)if (isSuccess)setPosts(prev=>prev.filter(post=>post.id!==id))}return (<><h1>Delete Post 👇</h1><br/><h2>Click a card</h2><divclassName="grid">{posts.map(post=>(<divclassName="card"key={post.id}onClick={()=>handleDelete(post.id)}><p>ID: <span>{post.id}</span></p><p>Title: <span>{post.title}</span></p><p>Body: <span>{post.body}</span></p></div>))}</div></>)}
🕯️ Handling errors.
To handle errors with axios, just go to our helper functions (src/utils/) and use a try/catch since we have been using async/await.
Inside the try we place all the code we want to execute.
Inside the catch, we are going to receive the error if something goes wrong, for example: error in the request, error in the server, among others.
This error that we receive by parameter, we can caste it to a type that does not offer axios that is AxiosError and thus to have the autocompletion.
This error has several properties, but the most common is the error message and the name of the error.
Finally note that in the catch we return an empty array, this is to comply with the contract of the function since we must return a promise type ResponseAPI[].
No doubt axios is a very powerful library, although I am not saying that it is mandatory, it is a very useful library that will serve you in some project 😉.
I hope you liked this post and I also hope I helped you to understand how to make basic HTTP requests with this library, an alternative to Fetch API. 🙌
If you know any other different or better way to perform this application feel free to comment.
I invite you to check my portfolio in case you are interested in contacting me for a project!. Franklin Martinez Lucas
🔵 Don't forget to follow me also on twitter: @Frankomtz361
🕯️ Demo.
I made some changes to the App.tsx so that you don't have to go to the code and comment out the component and test each type of request