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.

Read more ...


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:

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

subCreate.tsx
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.

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!.

Source Code.