How to use React query, Axios and a custom request processor to transform your data

Ama Victor
5 min readMar 25, 2023

--

React Query is a robust library designed for data management within React applications. It simplifies tasks such as data fetching, caching, and synchronization with the server, offering an elegant solution. In contrast, Axios stands out as a widely-used HTTP client for handling API requests in JavaScript applications. By combining React Query with Axios, we can create a powerful data management solution that is both easy to implement and maintain.

In this article, I will guide you through the process of integrating React Query with Axios to facilitate effortless API calls. We will leverage some custom hooks along with Axios, the request processor, to streamline our data management approach. Each step of the process will be accompanied by examples, allowing you to easily follow along and implement the solution in your own application. This article has been updated to reflect the latest enhancements and requirements of the React Query library.

Step 1: Setting up the Axios client and request processor.

Before proceeding with the implementation of React Query code, it’s essential to organize your project structure by creating a Utils folder. Within this folder, create a Network.js file where the request processor will be implemented. Follow these steps to set up your request processor:

import axios from "axios";
import { BASE_URL } from "./constants";

const client = axios.create({
baseURL: BASE_URL
})

export const request = async (options) => {
let token
const state = store.getState()
const userState = state?.user?.currentUser
if (userState === null) {
token = ""
}
else {
const { accessToken } = userState
token = accessToken
}
// Set the authorization header
token !== "" && (client.defaults.headers.common.Authorization = `Bearer ${token}`);

const onSuccess = (response) => {
return response?.data?.data;
};

const onError = (error) => {
return Promise.reject(error.response?.data);
};

return client(options).then(onSuccess).catch(onError);
};

This code performs several key functions: it sets up an Axios client with a specified baseURL and establishes the request processor. Assuming you’ve opted to utilize Redux for state management, with tokens stored within the Redux state, this code assists in extracting the access token using state.getStore(). Alternatively, you may choose to store tokens in cookies or any other preferred method.

The request function is central to this setup, aiding in setting headers, making requests, implementing robust error handling, and formatting the returned data. Additionally, there’s ample room for customization based on specific project requirements.

With the request function in place, you’re ready to create custom hooks within React Query to streamline API requests and further transform your data. Specifically, you can create useApiSend and useApiGet hooks for outbound and inbound requests, respectively.

import { 
useQuery,
useMutation,
useQueryClient
} from "@tanstack/react-query"


export const useApiGet = (key, fn, options) => useQuery({
queryKey: key,
queryFn: fn,
...options
})

export const useApiSend = (fn, success, error, invalidateKey, options) => {
const queryClient = useQueryClient();

return useMutation({
mutationFn: fn,
onSuccess: (data) => {
invalidateKey &&
invalidateKey.forEach((key) => {
queryClient.invalidateQueries(key);
});
success && success(data);
},
onError: error,
retry: 2,
...options,
});
};

In the useApiGet code snippet, you'll notice that it accepts a query key, which can be set to invalidate queries or perform other actions specific to a query. It also takes a queryFunction parameter, which is a function returning the request processor actions. Finally, the options parameter consists of React Query options that you may want to set for the query. This setup is straightforward and becomes clearer once implemented.

Similarly, useApiSend facilitates outbound requests such as POST, PUT, PATCH, DELETE, and more. Like its counterpart useApiGet, it accepts parameters including the fn (function), success parameter to define actions on success, error parameter to handle errors, the query key for invalidation, and React Query options.

Step 2: Using the useApiGet

Let’s dive into performing a GET request using the useApiGet custom hook freshly crafted from our custom hook kitchen! 😏 First, it's a good idea to create a folder named Urls to house functions that return the URLs for API requests. For instance, suppose we want to fetch post feeds from the server. Typically, API endpoints are organized according to their controllers. As a best practice, name the files in this folder according to the controller's name, as all URLs within the controller share the same starting name in the endpoint.

Urls folder structure
Inside the auth url folder

The images above are merely for explanatory purposes on how to structure your URL files. Now, let’s get back to the discussion. We’ll proceed to make a GET request for the “feeds” endpoint.

//The get feed url definition

import { request } from "../Utils"
export const getFeeds = () =>
request({
url: `/feed`,
method: "GET",
})
//home page where you get feeds from the server
import { getFeeds } from '../../../Urls';

const {
data,
isLoading,
error,
isError,
isLoadingError,
refetch } = useApiGet(
["feeds"],
getFeeds,
{
enabled: true,
refetchOnWindowFocus: true,
retry: 1
}
);


if (isLoading)
return <ScreenLoader />;

if (isError || isLoadingError)
return <ErrorPage
error={error?.message}
return (
<div>
{data?.map((item,index))=> <PostCard item={item} key={index}/>}
</div>
)

The code above gives a simple description on how you can make a get request using the useApiGet.

Step 3: Mutating Data

In addition to fetching data, sending data to the server is equally crucial. Here’s how we can proceed. Similar to handling GET requests, we’ll need to define functions to handle POST requests. Let’s create a POST request.

//create post function

import { request } from "../Utils"

export const createPost = (data) =>
request({
url: `/posts`,
method: "POST",
data
})
export const AddPost =()=>{
const [post, setPost] =useState()
const { mutate, isPending } = useApiSend(
createPost,
() => {
Toast.show({
type: "success",
text1: "Post add successfully"
})
},
(e) => {
Toast.show({
type: "error",
text1: `Couldn't add post ${e.message}`
})
}

}

const onSubmit =()=>{
mutate({
content:post
})
}
if(isPending){
return <ScreenLoader/>
}

return (
<div>
<input placeholder="enter post" value={post} onChange={(value)=> setPost(value)}/>
<button onClick={onSubmit}/>
</div>
)

Here, we’re creating a POST request. We start by defining the createPost function, which passes the URL, method, and data to the request processor to perform the request. Additionally, you can utilize Axios functions like axios.post, axios.get, axios.patch, etc., and create more customized API hooks based on your requirements. The useApiSend hook exposes all the methods and variables similar to React Query's useMutation, making it easier for you to make requests and manage loading and error states.

Putting it All Together:

In this article, I’ve demonstrated how to effectively utilize React Query with Axios as a request processor, along with custom hooks and an Axios client. I’ve provided examples for fetching and mutating data and illustrated how to integrate everything seamlessly into a React application.

By leveraging React Query with Axios, we can establish a robust data management solution that is both user-friendly and maintainable. It offers a straightforward and elegant approach to managing data fetching, caching, and synchronization with the server.

Thank you for reading and see you around!

--

--

Ama Victor

Software engineer | Simplifying web development | I write about how I fix challenges in my code...