React Query

Introduction

  • The library to handle data fetching, including retry, caching, error handling, so as to reduce the boilerplate code

  • Convenient to create custom hook to fetching or updating data in order to achieve separate of concern

Fresh & Stale State

  • Fresh: all the data must come from cache, no background refetching will be happened

  • Stale: data can still come from cache, but the background refetching can be happened

Default Behavior

  • Stale queries are refetched automatically in the background when:

    • New instances of the query mount

    • The window is refocused

    • The network is reconnected.

    • The query is optionally configured with a refetch interval.

  • If Query fails, will silently retry 3 times

  • The data of the inactive queries will be cached for 5 mins

  • The staleTime is 0

  • isLoading: Your query has no data and is currently loading for the first time

  • isFetching flag is not part of the internal state machine - it is an additional flag that will be true whenever a request is in-flight. You can be fetching and success, you can be fetching and error - but you cannot be loading and success at the same time.

  • React Query will attempt to compare the old state and the new and keep as much of the previous state as possible to prevent trivial re-rendering

Query Cancellation

  • Queries that unmount or become unused will be cancelled if you consume the AbortSignal or attach a cancel function to your Promise, the Promise will be cancelled (e.g. aborting the fetch)

import axios from 'axios'

const query = useQuery('todos', ({ signal }) =>
  axios.get('/todos', {
    // Pass the signal to `axios`
    signal,
  })
)
import axios from 'axios'

const query = useQuery('todos', () => {
  // Create a new CancelToken source for this request
  const CancelToken = axios.CancelToken
  const source = CancelToken.source()

  const promise = axios.get('/todos', {
    // Pass the source token to your request
    cancelToken: source.token,
  })

  // Cancel the request if React Query calls the `promise.cancel` method
  promise.cancel = () => {
    source.cancel('Query was cancelled by React Query')
  }

  return promise
})
  • The query can also be cancelled manually

const [queryKey] = useState('todos')

const query = useQuery(queryKey, async ({ signal }) => {
  const resp = await fetch('/todos', { signal })
  return resp.json()
})

const queryClient = useQueryClient()

return (
  <button onClick={(e) => {
    e.preventDefault()
    queryClient.cancelQueries(queryKey)
   }}>Cancel</button>
)

Setup Provider

  • To set up the provider for children, so that react-query can be used in component

  • To customize the default setting for library

import axios from "axios";
import { ReactNode } from "react";
import {
  DefaultOptions,
  QueryClient,
  QueryClientProvider as Provider,
} from "react-query";
import { ReactQueryDevtools } from "react-query/devtools";

const defaultOptions: DefaultOptions = {
  queries: {
    refetchOnReconnect: false,
    refetchOnWindowFocus: false,
    retry: (failureCount, error) =>
      failureCount < 3 &&
      !(axios.isAxiosError(error) && error?.response?.status === 401),
  },
};

const queryClient = new QueryClient({ defaultOptions });

interface QueryClientProviderProps {
  children: ReactNode;
}

export default function QueryClientProvider({
  children,
}: QueryClientProviderProps) {
  return (
    <Provider client={queryClient}>
      {children}
      <ReactQueryDevtools initialIsOpen={false} />
    </Provider>
  );
}
export default function App(){
    <QueryClientProvider>
        {children}
    </QueryClientProvider>
}

UseQuery

  • Handle fetching data when the page is mounted and the dependencies are changed

  • Data will be cached based on the query key

interface PageListApiResponse {
  results: Page[];
  maxPublishedPages: number;
}

export default function usePageListQuery(
  state: PageState,
  offset: number,
  options: UseQueryOptions<PageListApiResponse, AxiosError<any>> = {}
) {
  const axios = useContext(AxiosContext);

  return useQuery({
    queryKey: ["pageList", offset, state],
    queryFn: async ({ queryKey }) => {
      const { data } = await axios.get<PageListApiResponse>(
        `${process.env.REACT_APP_API_URL}/pages?_limit=10&_start=${queryKey[1]}&state=${queryKey[2]}`
      );
      return data;
    },
    ...options,
  });
}
const {
    data: { results: listPagesData = [], maxPublishedPages = 0 } = {},
    isLoading,
    isFetching,
    refetch,
  } = usePageListQuery(listingStatus, pageOffset, {
    onSuccess:(data)=> {...},
    onError: (err)=> {...},
    enabled: allCountData && allCountData.count > 0,
    select: ({ results, ...data }) => ({
      ...data,
      results: [...results].sort((a, b) => {
        if (a.url === homePageSettings?.home) return -1;
        if (b.url === homePageSettings?.home) return 1;
        return 0;
      }),
    }),
  });

UseMutation

  • Used to update data

export default function usePageUpdateMutation({
  onSuccess,
  ...options
}: MutationOptions<AxiosResponse<Page>, AxiosError<any>, Page> = {}) {
  const axios = useContext(AxiosContext);
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: (page: Page) =>
      axios.put<Page>(
        `${process.env.REACT_APP_API_URL}/pages/${page.id}`,
        page
      ),
    onSuccess: (data, variables, context) => {
      if (onSuccess) onSuccess(data, variables, context);
      // to clear the query, 
      // so that the query will be loaded again and trigger isLoading
      queryClient.invalidateQueries("allPageList");
      queryClient.invalidateQueries(["page", variables.id]);
      // set the data to the existing query directly
      queryClient.setQueryData(['test', variables.id], data)
    },
    ...options,
  });
}

Last updated

Was this helpful?