> For the complete documentation index, see [llms.txt](https://petercheng7788.gitbook.io/developer-note/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://petercheng7788.gitbook.io/developer-note/frontend/react/data-fetching/react-query.md).

# 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

{% embed url="<https://tkdodo.eu/blog/react-query-render-optimizations>" %}

{% embed url="<https://tkdodo.eu/blog/status-checks-in-react-query>" %}

{% embed url="<https://tkdodo.eu/blog/react-query-as-a-state-manager>" %}

## 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)&#x20;

```typescript
import axios from 'axios'

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

```typescript
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

```typescript
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>
)
```

{% embed url="<https://tanstack.com/query/v3/docs/framework/react/guides/query-cancellation>" %}

## 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

```typescript
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>
  );
}
```

```typescript
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

```typescript
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,
  });
}
```

```typescript
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;
      }),
    }),
  });
```

{% embed url="<https://tkdodo.eu/blog/react-query-and-type-script>" %}

{% embed url="<https://tkdodo.eu/blog/effective-react-query-keys>" %}

## UseMutation

* Used to update data

```typescript
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,
  });
}
```

{% embed url="<https://tkdodo.eu/blog/mastering-mutations-in-react-query>" %}
