Introduction
Consuming data from an API or some sort of external source is always necessary throughout our applications and the traditional method using fetch API and the useEffect hook is a bit verbose, time-consuming and not advised. Axios emerged as a solution for that and currently, Axios is still dominant in use.
This is where React Query comes into play, React Query is responsible for managing asynchronous operations between server and client and it gives us many advantages.
So for this article, we will be talking about React Query v5 with Axios to provide an excellent combination of technologies, so that you can handle your requests, data fetching, asynchronous state handling, caching, garbage collection, memory management, refetching, loading states, react query dev tools and some examples.
Installation
npm i @tanstack/react-query
yarn add @tanstack/react-query
pnpm add @tanstack/react-query
React-Query setup
After installation, the first thing we need to do in our React.js application is to wrap our entire application with QueryClientProvider and pass a client prop to it. Now React-Query is ready to be used.
import React from 'react';
import ReactDOM from 'react-dom/client';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
const queryClient = new QueryClient();
export function App() {
return (
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
);
}
We can also pass some initial properties to the Query Client:
React-Query DevTools - Optional
You can also install React-Query DevTools. React-Query Dev Tools will help you visualize data and everything that is going on and it is a tool that will give you important info to help you debug your application.
We have to install it since it is a separate package provided by Tanstack.
Here's how to install it:
npm i @tanstack/react-query-devtools
yarn add @tanstack/react-query-devtools
pnpm add @tanstack/react-query-devtools
Now let's configure React-Query DevTools in our application, we can pass it inside the QueryClientProvider as a sibling of the main component . We can also pass 3 properties to define the position of the Icon that triggers the panel and the DevTools Panel itself.
- initialIsOpen - If you pass this then DevTools is going to be open by default
- position - The panel position, top, bottom, left or right
- buttonPosition - The position of the button that opens the panel, possible values are: top-left, top-right, bottom-left, bottom-right.
import React from 'react';
import ReactDOM from 'react-dom/client';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
const queryClient = new QueryClient();
export function App() {
return (
<QueryClientProvider client={queryClient}>
<App />
<ReactQueryDevtools
initialIsOpen
position='right'
buttonPosition='bottom-right'
/>
</QueryClientProvider>
);
}
Implementation
With the initial configurations out of the way, we can now start using it for real.
The basics of React Query are 2 things, query and mutation. Query is when we fetch data from somewhere and mutation is when data is changed, this change can happen for several reasons, an update, data has become stale, a post, a put, patch request and React-Query will handle that for us.
To handle the requests to the API in this article we will be using Axios, to install it follow the steps:
npm i axios
yarn add axios
pnpm add axios
I created an API configuration file to make requests to jsonplaceholder api to bring a list of the posts.
import axios from 'axios';
const todosApi = axios.create({
baseURL: 'https://jsonplaceholder.typicode.com'
});
export async function getTodos () {
const response = await todosApi.get('/todos');
return response.data;
}
export async function addTodo (todo) {
const response = await todosApi.post('/todos', todo);
return response.data;
}
export async function editTodo (todo) {
const response = await todosApi.patch(`/todos/${todo.id}`, todo);
return response.data;
}
export async function removeTodo ({ id }) {
const response = await todosApi.delete(`/todos/${id}`, id);
return response.data;
}
export default todosApi;
And I created a TodoList component where we will combine the code using Axios and React-Query.
import { useQuery, useQueryClient, useMutation } from '@tanstack/react-query';
import { getTodos, addTodo } from '../../api/api';
export default function TodoList() {
const queryClient = useQueryClient();
const { data, isLoading, } = useQuery({
queryKey: ['todos'],
queryFn: getTodos
});
const { mutateAsync } = useMutation({
mutationFn: addTodo,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['todos'] });
}
});
if (isLoading) {
return <p>Loading posts...</p>;
}
return (
<main>
<h1>Todo List</h1>
<ul>
{data.map((todo) => (
<li key={todo.id}>
{todo.title}
</li>
))}
</ul>;
<button onClick={() => {
mutateAsync({
id: Math.random(),
title: 'Finish article'
});
}}>
Add todo
</button>
</main>
);
}
If we weren't using React-Query we would have to create, loading states using useState, handle the errors, caching, as you can see in the image below:
As said before React-Query works with 2 concepts query and mutation and React-Query provides the hooks useQuery( ) and useMutation( ) for us to handle our asynchronous operations between Frontend and Backend.
The hook useQuery is used to fetch data and to do that we import those functions created in the API code previously provided. We are importing both code from React-Query and also the API that is using Axios.
When we use the hook useQuery( ) is common to pass an object with a queryKey which can be any name you want but it should be related to what sort of operation is being done to be more semantic, in our case we named it 'todos' because that is exactly what we are dealing with, we are fetching a list of todos. React-Query will handle cache, memory optimization, garbage collection and other things with this value.
We also pass a queryFn which is the function that will execute the request which in our case is being done by Axios.
const { data, isLoading } = useQuery({
queryKey: ['todos'],
queryFn: getTodos
});
In the code above the getTodos function is in the api file and it was imported into this component to be used here with React-Query. And to have access to the posts fetched all we have to do is map through the data which is the first value destructured and now we have all the posts in a li tag.
<>
<h1>Todo List</h1>
<ul>
{data.map((todo) => (
<li key={todo.id}>
{todo.title}
</li>
))}
</ul>;
</>
We don't have to create states [isLoading, setIsLoading] because all of that and much more is already built-in and provided by React-Query.
if (isLoading) {
return <p>Loading posts...</p>;
}
As for the operations that will involve changes in the data, we will use the hook useMutation( ).
We can destructure many props from useMutation( ) and pass an object to it as in the useQuery( ) hook. But now we pass a mutationDn which is the function that will change data, it can be a request to the api or something.
We can also pass a callback function to onSuccess, onError to handle the success or error cases of the request.
const { mutateAsync } = useMutation({
mutationFn: addTodo,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['todos'] });
}
});
We can use the mutateAsync function that was destructured and use it in a button to be triggered via onClick( ) or something.
<button onClick={() => {
mutateAsync({
id: Math.random(),
title: 'Finish article'
});
}}>
Conclusion
There are tons of other things that can be done with React-Query, we only scratched the surface here. To have more details and further info you can always turn to the official docs that will be provided below.
React-Query is a very powerful technology and you should use it in your projects along with Axios, you will have a powerful combination of tools to handle a lot of things that you would have to do by yourself, many useStates and useEffects can be avoided by using React-Query properly.
References
https://tanstack.com/query/v5/docs/react/overview
Top comments (0)