---
title: "React Query to simplify data fetching in Neeto"
description: "React Query integration in react applications"
canonical_url: "https://www.bigbinary.com/blog/react-query"
markdown_url: "https://www.bigbinary.com/blog/react-query.md"
---

# React Query to simplify data fetching in Neeto

React Query integration in react applications

- Author: Mohit Harshan
- Published: April 10, 2023
- Categories: ReactJS

## Introduction

[React Query](https://tanstack.com/query/v3/docs/react/overview) is a powerful
tool that simplifies the data fetching, caching and synchronization with the
server by providing a declarative and composable API of hooks. It was created by
Tanner Linsley in 2020 and has gained a lot of popularity since then.

It uses a cache-first approach to optimize the user experience by reducing the
number of network requests made by the application. React Query also provides
built-in support for features like pagination, optimistic updates, and retrying
failed requests.

While building [neeto](https://neeto.com) we started using React Query and we
learned a few things about how to effectively use it. Below are some of the
lessons we learned.

## Major advantages of using React Query

1. **Simplifies data fetching - It erases a lot of boilerplate and makes our
   code easier to read.**

   Here is a comparison of code structure with and without using `react-query`
   to fetch data from the server:

   Without React Query:

   ```js
   const [isLoading, setIsLoading] = useState(false);
   const [data, setData] = useState([]);
   const [error, setError] = useState(null);

   useEffect(() => {
     setIsLoading(true);
     (async () => {
       try {
         const data = await fetchPosts();
         setData(data);
       } catch (error) {
         setError(error);
       } finally {
         setIsLoading(false);
       }
     })();
   }, []);
   ```

   With React Query:

   ```js
   const { isInitialLoading, data, error } = useQuery(["posts"], fetchPosts);
   ```

   React Query provides a `useQuery` hook to fetch data from the server. The
   entire `useEffect` was replaced with a single line with React Query. The
   `isInitialLoading`, `error` and the data state is handled out of the box.

2. **Provides tools to improve user experience and prevent unnecessary API
   calls.**

   React Query provides a powerful caching mechanism that can help prevent
   unnecessary API calls by storing the results of API requests in a cache. When
   a component requests data from the API using React Query, it first checks if
   the data is already present in the cache. If the data is present and hasn't
   expired, React Query returns the cached data instead of making a new API
   call.

   Here's an example:

   Suppose we have a component that displays a list of todos fetched from an API
   endpoint at `https://api.example.com/todos`. We can use React Query to fetch
   the data and store it in the cache like this:

   ```js
   import { useQuery } from "@tanstack/react-query";

   const TodoList = () => {
     const { data, isInitialLoading, isError } = useQuery(
       ["todos"],
       () => axios.get("https://api.example.com/todos"),
       {
         staleTime: 10000, // data considered "fresh" for 10 seconds
       }
     );

     if (isInitialLoading) {
       return <div>Loading...</div>;
     }

     if (isError) {
       return <div>Error fetching data</div>;
     }

     return (
       <ul>
         {data.map(todo => (
           <li key={todo.id}>{todo.title}</li>
         ))}
       </ul>
     );
   };
   export default TodoList;
   ```

   In this example, `useQuery` is used to fetch the data from the API endpoint.
   The first argument to useQuery is a unique key that identifies the query, and
   the second argument is a function that returns the data by making a request
   to the API endpoint.

   Now, suppose the user navigates away from the `TodoList` component and then
   comes back to it, if the data in the cache hasn't expired, React Query will
   return the cached data instead of making a new API call, which can help
   prevent unnecessary network requests.

   If the cache time has expired, React Query will not show the loading state
   because it will automatically trigger a new API call to fetch the data.
   During this time, React Query will return the stale data from the cache while
   it waits for the new data to arrive. This means that our component will
   continue to display the old data until the new data arrives, but it won't
   show a loading state because it's not waiting for the data to load.

   Once the new data arrives, React Query will update the cache with the new
   data and then return the updated data to the component.

3. **Request Retries - Ability to retry a request in case of errors.**

   React Query provides built-in support for request retries when API requests
   fail. This can be useful in situations where network connectivity is
   unreliable, or when the server is under heavy load and returns intermittent
   errors.

   When we use useQuery or useMutation from React Query, we can provide an
   optional retry option that specifies the number of times to retry a failed
   request. Here's an example:

   ```js
   const { data, isInitialLoading, isError } = useQuery(
     ["todos"],
     () => axios.get("https://api.example.com/todos"),
     {
       retry: 4, // retry up to 4 times
     }
   );
   ```

   In this example, we're using `useQuery` to fetch data from an API endpoint.
   We've set the retry option to 4, which means that React Query will retry the
   API request up to 4 times if it fails. If the API request still fails after 4
   retries, React Query will return an error to our component.

   We can also customize the behavior of request retries by providing a function
   to the `retryDelay` option. This function should take the current retry count
   as an argument and return the number of milliseconds to wait before retrying
   the request. Here's an example:

   ```js
   const { data, isInitialLoading, isError } = useQuery(
     ["todos"],
     () => axios.get("https://api.example.com/todos").then(res => res.json()),
     {
       retry: 3, // retry up to 3 times
       retryDelay: retryCount => Math.min(retryCount * 1000, 30000), // wait 30 seconds between retries
     }
   );
   ```

4. **Window Focus Refetching - Refetching based on application tab activity.**

   Window Focus Refetching is a feature of React Query that allows us to
   automatically refetch our data when the user returns to our application's tab
   in their browser. This can be useful if we have data that changes frequently
   or if we want to ensure that our data is always up to date.

   React Query will automatically refetch our data when the user returns to our
   application's tab in their browser. Please note that `refetchOnWindowFocus`
   is true by default.

   To disable it, we can do it per query or globally in the query client.

   Disabling per-query:

   ```js
   useQuery(["todos"], fetchTodos, { refetchOnWindowFocus: false });
   ```

   Disabling globally:

   ```js
   const queryClient = new QueryClient({
     defaultOptions: {
       queries: {
         refetchOnWindowFocus: false, // default: true
       },
     },
   });
   ```

## How to integrate React Query

1.  We can install React Query using `npm` or `yarn`. Open the terminal and
    navigate to the project directory. Then, run one of the following commands:

    Using npm:

         npm install @tanstack/react-query

    Using yarn:

         yarn add @tanstack/react-query

2.  To integrate react query, first we need to wrap it a `QueryClientProvider`.
    In the `App.jsx` file (or whichever is the topmost parent),

    ```js
    import queryClient from "utils/queryClient";
    import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
    import { QueryClientProvider } from "@tanstack/react-query/reactjs";

    const App = () => (
      <QueryClientProvider client={queryClient}>
        <Main />
        <ReactQueryDevtools initialIsOpen={false} />
      </QueryClientProvider>
    );
    export default App;
    ```

    `ReactQueryDevtools` is a great tool for debugging and optimizing our React
    Query cache. With `ReactQueryDevtools`, we can view the current state of our
    queries, including the query status, data, and any errors that may have
    occurred. We can also view the cache entries and manually trigger queries or
    invalidate cache entries.

    ```js
    import { QueryClient } from "@tanstack/react-query";

    const queryClient = new QueryClient({
      defaultOptions: {
        queries: {
          staleTime: 100_000,
        },
      },
    });

    export default queryClient;
    ```

    The `queryClient` holds the query cache. The query client provider is a
    React context provider, which allows us to access the client (and thus the
    cache) without passing it as a prop explicitly. Every time we call
    `useQueryClient()` , we get access to this client. The `useQuery` &
    `useMutation` hooks use the query client internally.

    `staleTime` is the duration until a query transitions from fresh to stale.
    As long as the query is fresh, data will always be read from the cache only.
    `cacheTime`: The duration until inactive queries will be removed from the
    cache. This defaults to 5 minutes.

    Most of the time, if we want to change one of these settings, it's the
    `staleTime` that needs adjusting. We have rarely ever needed to tamper with
    the `cacheTime`.

3.  React query provides us mainly two hooks to fetch and mutate data:
    `useQuery` and `useMutation`.

    `useQuery`:

    ```js
    const { data, isInitialLoading, isError } = useQuery(["todos"], () =>
      axios.get("https://api.example.com/todos")
    );
    ```

    In this example, we're using useQuery to fetch data from an API endpoint at
    `https://api.example.com/todos`. The first argument to `useQuery` is a
    unique key that identifies this query. The second argument is a function
    that returns a promise that resolves to the data we want to fetch.

    The `useQuery` hook returns an object with three properties: `data`,
    `isInitialLoading`, and `isError`. In addition to these, `useQuery` also
    returns various other properties like `isSuccess`, `isFetching` etc and
    callbacks like `onError`,`onSettled`,`onSuccess` etc. The data property
    contains the data that was fetched, while `isInitialLoading` and `isError`
    are booleans that indicate whether the data is currently being fetched or if
    an error occurred while fetching it.

    `useMutation`:

    ```js
    import { useQueryClient, useMutation } from "@tanstack/react-query";

    const queryClient = useQueryClient();

    const { mutate, isLoading } = useMutation(
      data =>
        axios.post("https://api.example.com/todos", {
          body: JSON.stringify(data),
        }),
      {
        onSuccess: () => {
          queryClient.invalidateQueries("todos");
        },
      }
    );
    ```

    In this example, we're using `useMutation` to send data to an API endpoint
    at `https://api.example.com/todos` using a `POST` request. The first
    argument to `useMutation` is a function that takes the data we want to send
    and returns a promise that resolves to the response from the API.

    The second argument to `useMutation` is an options object that allows us to
    specify a callback `onSuccess` function to be called when the mutation
    succeeds. In addition to `onSuccess`, `useMutation` provides us various
    other callbacks such as `onError`, `onMutate`,`onSettled` etc. In this case,
    we're using the `onSuccess` option to invalidate the `todos` query in the
    `queryClient`. This will cause the query to be refetched the next time it's
    requested, so the updated data will be displayed.

## The standards we follow at BigBinary

1. The `QueryClientProvider` is wrapped in the `App.jsx` file.

2. The `queryClient` is placed in `utils/queryClient.js` file and it can set the
   default values for stale time, cache time etc.

3. We store the query keys in `constants/query.js` file.

   ```js
   export const DEFAULT_STALE_TIME = 3_600_000;

   export const QUERY_KEYS = {
     WEB_PUSH_NOTIFICATIONS: "web-push-notifications",
     WIDGET_SETTINGS: "widget-settings",
     WIDGET_INSTALLATION_SCRIPT: "widget-installation-script",
   };
   ```

4. To fetch and mutate data, we can create API hooks in `hooks/reactQuery`
   folder and based on the structure of the app, we create folders or files
   inside this folder. For example, we can have a `hooks/reactQuery/settings`
   folder to separate settings-related hooks from other API hooks.

5. React query hook files are named in the format `use*Api` where `*` is the
   specific set of `apis` we are trying to use.

   For example inside `hooks/reactQuery/useIntegrationsApi.js`:

   ```js
   import { prop } from "ramda";
   import { useMutation, useQuery } from "react-query";
   import { DEFAULT_STALE_TIME, QUERY_KEYS } from "src/constants/query";

   import integrationsApi from "apis/integrations";
   import queryClient from "utils/queryClient";

   const { THIRD_PARTY_APPS } = QUERY_KEYS;

   const onMutation = () => queryClient.invalidateQueries(THIRD_PARTY_APPS);

   export const useUpdateIntegration = () =>
     useMutation(({ id, options }) => integrationsApi.update(id, options), {
       onSuccess: onMutation,
     });

   export const useFetchIntegrations = () =>
     useQuery(THIRD_PARTY_APPS, integrationsApi.fetchThirdPartyApps, {
       staleTime: DEFAULT_STALE_TIME,
       select: prop("thirdPartyApps"),
     });
   ```

6. We import the api hooks from the `use*Api` hook we have created and use it in
   the file we would like to fetch or mutate queries.

   For example:

   ```js
   import {
     useFetchIntegrations,
     useUpdateIntegration,
   } from "hooks/reactQuery/useIntegrationsApi";

   const Integrations = () => {
     const { data: integrationApps, isLoading } = useFetchIntegrations();
     const { mutate: updateIntegration, isLoading: isInstalling } =
       useUpdateIntegration();

     return (
       <div>
         <DataTable data={data} />
       </div>
     );
   };
   export default Integrations;
   ```

At BigBinary, we are using React Query v3. Since the update from v3 to v4, there
has been some breaking changes. For more information, refer:
[Documentation](https://tanstack.com/query/v4/docs/react/guides/migrating-to-react-query-4#react-query-is-now-tanstackreact-query)

## Links

- [Human page](https://www.bigbinary.com/blog/react-query)
