React Query with React
Author : JaNakh Pon , September 23, 2021
Tags
What is React Query ?
React Query is often described as the missing data-fetching library for React, but in more technical terms, it makes fetching, caching, synchronizing and updating server state in your React applications a breeze.
Out of the box, React applications do not come with an opinionated way of fetching or updating data from your components so developers end up building their own ways of fetching data. This usually means cobbling together component-based state and effect using React hooks, or using more general purpose state management libraries to store and provide asynchronous data throughout their apps.
While most traditional state management libraries are great for working with client state, they are not so great at working with async or server state. This is because server state is totally different.
Summary
In this article, we are going to use react-query
with axios
for fetching/caching/revalidating data for our react app.
The reason we want to use React Query is simple, we want to fetch the data and cache it in client side, and also refetch the data on certain conditions to make sure our frontend state is updated/syncronized with the server state.
To be able to use React Query in our frontend app, we will need to install and configure react-query
in our root index.tsx
and we will also enable ReactQueryDevtools
to be able to inspect the data tree.
And to use react-query's Query
and Mutation
in our components, we will create custom react hooks for query and mutation under src/Components/hooks/*
folder.
Basically, for GET
method, we will use axios
to write a function that will do the data fetching and we will assign it to the queryFn
ofuseQuery
so we can refetch/cache the data whenever or however we want it.
And for CREATE/UPDATE/DELETE
methods, we will use useMutation
with axios
to mutate the data and we will update the cached data with the updated response and revalidate the Query once we finish the mutation. As as result, we will use the updated local data from the cache until the data from revalidation is ready.
Installation & Setup
Firstly, let's install the react-query
dependency 😉:
> npm i react-query --save
And let's configure it in index.tsx
:
import React from 'react';
import ReactDOM from 'react-dom';
import { QueryClient, QueryClientProvider } from 'react-query';
import { ReactQueryDevtools } from 'react-query/devtools'
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
const queryClient = new QueryClient({
defaultOptions: {
queries: {
retry: false,
refetchOnWindowFocus: true,
},
},
})
ReactDOM.render(
<React.StrictMode>
<QueryClientProvider client={queryClient}>
<App />
{/* Enabling ReactQueryDevTools */}
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
</React.StrictMode>,
document.getElementById('root')
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
custom useQuery & useMutation Hooks
Let's create custom hooks by typing the commands below ...
> cd src/Components && mkdir hooks && cd hooks && touch useQueryTasks.ts useMutateTasks.ts && cd ....
And "useQuery" hooks
import { useQuery } from 'react-query'
import axios from 'axios'
import { TaskType, SortType, CountType, SearchResponse } from '../../Types/task.type'
export const useQueryTasks = (page: number, sort: string, order: SortType) => {
const getTasks = async () => {
const { data } = await axios.get<TaskType[]>(
`http://localhost:3001/api/v1/todos?page=${page}&sort=${sort}&order=${order}`
)
return data
}
return useQuery<TaskType[], Error>({
queryKey: `todos?page=${page}&sort=${sort}&order=${order}`,
queryFn: getTasks,
staleTime: 0,
refetchOnWindowFocus: true,
// cacheTime: 5000,
refetchInterval: 5000,
})
}
export const useQueryCount = () => {
const getCount = async () => {
const { data } = await axios.request<CountType>({
method: 'get',
url: `http://localhost:3001/api/v1/todos/count`
})
return data
}
return useQuery<CountType, Error>({
queryKey: `count`,
queryFn: getCount,
staleTime: 0,
refetchOnWindowFocus: true,
// cacheTime: 5000,
refetchInterval: 5000,
})
}
export const useQuerySearch = (text: string) => {
const searchContent = async () => {
const { data } = await axios.request<SearchResponse>({
method: 'get',
url: `http://localhost:3001/api/v1/todos/search?text=${text}`
})
return data
}
return useQuery<SearchResponse, Error>({
queryKey: `search?=text=${text}`,
queryFn: searchContent
})
}
And "useMutation" hooks
import axios from 'axios'
import { useQueryClient, useMutation } from 'react-query'
import { CreateTask, TaskType } from '../../Types/task.type'
export const useMutateTask = () => {
const queryClient = useQueryClient()
const createTaskMutation = useMutation(
(task: CreateTask) =>
axios.post(`http://localhost:3001/api/v1/todos`, task),
{
onSuccess: (res) => {
const preTasks = queryClient.getQueryData<TaskType[]>('todos?page=1&sort=updated_at&order=DESC')
if (preTasks) {
queryClient.setQueryData<TaskType[]>('todos?page=1&sort=updated_at&order=DESC', [
...preTasks,
res.data,
])
}
queryClient.invalidateQueries()
},
}
)
const updateTaskMutation = useMutation(
(task: TaskType) =>
axios.put(`http://localhost:3001/api/v1/todos/${task.id}`, task),
{
onSuccess: async (res, variables) => {
const preTasks = queryClient.getQueryData<TaskType[]>('todos?page=1&sort=updated_at&order=DESC')
if (preTasks) {
queryClient.setQueryData<TaskType[]>(
'todos?page=1&sort=updated_at&order=DESC',
preTasks.map((task) =>
task.id === variables.id ? res.data : task
)
)
}
queryClient.invalidateQueries()
},
}
)
const deleteTaskMutation = useMutation(
(id: number) =>
axios.delete(`http://localhost:3001/api/v1/todos/${id}`),
{
onSuccess: (res, variables) => {
const preTasks = queryClient.getQueryData<TaskType[]>('todos?page=1&sort=updated_at&order=DESC')
console.log(variables, "HEHE")
if (preTasks) {
queryClient.setQueryData<TaskType[]>('todos?page=1&sort=updated_at&order=DESC', preTasks.filter((task) => task.id !== variables))
}
queryClient.invalidateQueries()
},
}
)
return { deleteTaskMutation, createTaskMutation, updateTaskMutation }
}
Implementation
Since we now have custom hooks for Query and Mutation, we will just need to replace plain axios fetching methods with useQueryTasks and useMutateTask to app.tsx.
import { useQueryTasks, useQueryCount, useQuerySearch } from './Components/hooks/useQueryTasks'
import { useMutateTask } from './Components/hooks/useMutateTasks'
const App = () => {
const { data: datacount } = useQueryCount()
const { data: searcheddata } = useQuerySearch(searchval)
const { createTaskMutation, updateTaskMutation, deleteTaskMutation } = useMutateTask()
.
.
.
const handleSave = async (e: FormEvent) => {
e.preventDefault()
createTaskMutation.mutate(task)
setTask({
title: "",
text: ""
})
}
const handleDelete = async (delid: number) => {
deleteTaskMutation.mutate(delid)
}
const handleComplete = async (e: ChangeEvent, task: TaskType) => {
const { checked } = e.target as HTMLInputElement;
updateTaskMutation.mutate({ ...task, completed: checked })
}
if (status === "loading") {
return (<div className="container mx-auto lg:my-32 md:my-30 sm:my-15 ">
<h1> Loading ... </h1>
</div>)
}
.
.
.
}
export default App;
Everything should be working by now. And you can also check the client side caching
in action by opening devtools/network/throtting to slow 3G.
Also, there will be a React Query icon in the bottom-left corner of the site where you can open the ReactQueryDevTools playground!.