DEV Community

Cover image for πŸ“š RTK Query: Understanding useQuery Hook
Raynaldo Sutisna
Raynaldo Sutisna

Posted on • Updated on

πŸ“š RTK Query: Understanding useQuery Hook

Introduction

It's been a while since I last wrote about RTK Query. I realized some people left questions in my previous blog about RTK Query, so I decided to write more about it. Hopefully, it will be helpful for some people. I want to share my deep understanding of the useQuery hook, specifically in this documentation.

useQuery

If you haven't read my previous blog, I recommend to start reading the πŸ“š RTK Query Tutorial (CRUD). I will refer to that in this blog

UseQuery

A React hook that automatically triggers fetches of data from an endpoint, 'subscribes' the component to the cached data, and reads the request status and cached data from the Redux store. The component will re-render as the loading status changes and the data becomes available.

That is a description according to the RTK Query docs. From my perspective, I just think about fetching, but it's not a simple fetching. RTK Query gives us many features rather than just a simple fetch. I already talked about this in my previous blog. Once again, go there before going through this blog! Sorry another reminder to make sure you are not lost here.

Let's go to the main reason that I wrote this blog. In my previous blog, I had my repository to give you an example of how to use useQuery

  const {
    data: albums = [],
    isLoading,
    isFetching,
    isError,
    error,
  } = useGetAlbumsQuery(page);
Enter fullscreen mode Exit fullscreen mode

https://github.com/raaynaldo/rtk-query-setup-tutorial/blob/main/src/components/Albums.js#L11-L17

It's just some options and results.

The full feature of useQuery is like this

  const {
    data: albums = [],
    originalArgs,
    currentData,
    requestId,
    endpointName,
    startedTimeStamp,
    fulfilledTimeStamp,
    isUninitialized,
    isLoading,
    isFetching,
    isError,
    error,
    refetch,
  } = useGetAlbumsQuery(page, {
    skip: true,
    selectFromResult: (result) => result,
    pollingInterval: 5000,
    refetchOnFocus: true,
    refetchOnReconnect: true,
    refetchOnMountOrArgChange: true,
  });
Enter fullscreen mode Exit fullscreen mode

That's a lot right? I'm pretty sure, we will not use all of these at the same time. I just want to share their purposes, so you can utilize them in the future.

Before I explain the useQueryOptions in the useQuery, I should let you know that some options such as refetchOnFocus, refetchOnReconnect, and refetchOnMountOrArgChange could be use as createApi parameters as well. Check the docs here.
If we are using createAPI, all the useQuery from the API will follow those configurations.

🟠 UseQueryOptions

This is the second parameter of the useQuery when you call it in your component.

useGetAlbumsQuery(arg: any, option: UseQueryOptions)
Enter fullscreen mode Exit fullscreen mode

keep in mind that the options are an object, so you can use them like this.

 useGetAlbumsQuery(page, {
    skip: true,
    selectFromResult: (result) => result,
    pollingInterval: 5000,
    refetchOnFocus: true,
    refetchOnReconnect: true,
    refetchOnMountOrArgChange: true,
  })
Enter fullscreen mode Exit fullscreen mode

Let's delve into every attribute on the object.

All code examples that I use will refer to this file, so you feel free to explore the project

πŸ”Έ skip?: boolean

The default is false.
If we set the skip to true, the fetching will not run at first, until the skip is updated to false.

  const {
    data: albums = [],
    isLoading,
    isFetching,
    isError,
    error,
  } = useGetAlbumsQuery(page, {
    skip: true,
  });
Enter fullscreen mode Exit fullscreen mode

Maybe for the real-life use case, we don't want to fetch the data if the argument is not ready yet. We can utilize the skip to be like this.

  const {
    data: albums = [],
    isLoading,
    isFetching,
    isError,
    error,
  } = useGetAlbumsQuery(page, {
    skip: page === undefined,
  });
Enter fullscreen mode Exit fullscreen mode

In the next GIF, I added a new state called skip, so I can modify it in react dev tools.

Skip

Technically, if the skip is true, it will either not start the fetch or will remove the data that already has been fetched. Just want to be clear, if the data that has been fetched is hidden because this skip is true, and you update the skip to be false, it will only call the cache data. I proofed this because the requestId is the same. I will explain more about requestId later.

skip-requestId

πŸ”Έ selectFromResult?: boolean

It's one of the interesting options here, we can do more flexibility here. This could override the useQueryResult (which I'll explain later in the next section).

I give an example of how could I update the result data to be upper case.

  const {
    data: albums = [],
    isLoading,
    isFetching,
    isError,
    error,
  } = useGetAlbumsQuery(page, {
    selectFromResult: (result) => {

      result.data = result.data.map((album) => ({
        ...album,
        title: album.title.toUpperCase(),
      }));

      return result;
    },
  });
Enter fullscreen mode Exit fullscreen mode

One thing you need to make sure that if you want to utilize the the result value from RTK Query, you must return the original result. That's why I just update the result.data, so I can still use the data, isLoading, 'isFetching', 'isError', and 'error'

Also, you can create another option if you add a new attribute to the return value of the function. To be honest, I'm not sure if it's a good practice or not, but it works.

Try to update with this.

  const {
    data: albums = [],
    isLoading,
    isFetching,
    isError,
    error,
    newResult,
  } = useGetAlbumsQuery(page, {
    selectFromResult: (result) => {
      result.data = result.data?.map((album) => ({
        ...album,
        title: album.title.toUpperCase(),
      }));

      result.newResult = 'test newResult';
      return result;
    },
  });

  console.log({ newResult });
Enter fullscreen mode Exit fullscreen mode

πŸ”Έ pollingInterval?: number

Be cautious when using this option. It will cost a lot of API fetch requests. It will tell the redux toolkit to refetch your API on specify time. The number for this pollingInterval refers to time in milliseconds.

  const {
    data: albums = [],
    isLoading,
    isFetching,
    isError,
    error,
  } = useGetAlbumsQuery(page, {
    pollingInterval: 2000,
  });
Enter fullscreen mode Exit fullscreen mode

polling interval

If you see my data is a little bit flashy, it's a quick loading component that is showing. I make my useQuery refetch the data for every 2 seconds.

πŸ”Έ refetchOnFocus?: boolean

Another option to refetch the data rather than using time interval, we can utilize this feature, which depends on the user interactions. When the users is focused on the browser or tab, and they come back to the page it will fetch the data again from the API.

  const {
    data: albums = [],
    isLoading,
    isFetching,
    isError,
    error,
  } = useGetAlbumsQuery(page, {
    refetchOnFocus: true,
  });
Enter fullscreen mode Exit fullscreen mode

refetchOnFocus

πŸ”Έ refetchOnReconnect?: boolean

Similar to the refetchOnFocus, it's just a different trigger of the refetch. It will do the refetch if the browser is disconnected from the internet and reconnect again to the internet. How to test? Attempt to disconnect from your Wi-Fi connection.

  const {
    data: albums = [],
    isLoading,
    isFetching,
    isError,
    error,
  } = useGetAlbumsQuery(page, {
    refetchOnReconnect: true,
  });
Enter fullscreen mode Exit fullscreen mode

refetchOnReconnect

πŸ”Έ refetchOnMountOrArgChange?: boolean

I should apologize for not fully understanding how this option works. If you have insights into this feature, please leave a comment. πŸ™

πŸ”΅ UseQueryResult

It's the result of calling the useQuery function. It may not be necessary to use all of its features.

Additionally, you can see mostly the result from the redux dev tools extension as well. Download the extension here.
Google Chrome: https://chromewebstore.google.com/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd?pli=1
Mozilla Firefox: https://addons.mozilla.org/en-US/firefox/addon/reduxdevtools/

redux dev tools

πŸ”Ή data

It's the result of the fetch data.

  const {
    data: album = [],
  } = useGetAlbumsQuery(page);
Enter fullscreen mode Exit fullscreen mode

If you're wondering why I renamed and assigned a default value to data as data: album = []

In order to differentiate different useQuery data, I prefer to rename it rather than data, and it could be ambiguous with another query result.

For default value using an empty array, makes our code to be less complicated when we want to render the data. We don't need to add any conditional render or even an optional chaining (?.)

{albums.map((album) => ( ... ))}
Enter fullscreen mode Exit fullscreen mode

πŸ”Ή isLoading

it's a boolean status that refers to the first fetch. Keep in mind it's only before the data is existed until the first fetch. It's different with isFetching

  const {
    isLoading,
  } = useGetAlbumsQuery(page);
Enter fullscreen mode Exit fullscreen mode

πŸ”Ή isFetching

This is different from the isLoading because this status depends on the refetching situation. For example, if the data is already fetched for the first time, and RTK Query does refetch to update the cache, it will refer to this status.

  const {
    isFetching,
  } = useGetAlbumsQuery(page);
Enter fullscreen mode Exit fullscreen mode

If you remember some options that I explained before, such as refetchOnFocus, refetchOnReconnect, and pollingInterval. It will affect the isFetching, not the isLoading

isFetching

This is why I handle loading with these two statuses.

  if (isLoading || isFetching) {
    return <div>loading...</div>;
  }
Enter fullscreen mode Exit fullscreen mode

πŸ”Ή isSuccess

Status for showing the fetching or refetching is successful. it is opposite of isError status.

  const {
    isSuccess,
  } = useGetAlbumsQuery(page);
Enter fullscreen mode Exit fullscreen mode

πŸ”Ή isError

Status for showing the fetching or refetching is failed or error. it opposites of isSuccess status. I usually utilize this status for handling the error and showing the error message.

  const {
    isError,
  } = useGetAlbumsQuery(page);
Enter fullscreen mode Exit fullscreen mode

error and success
The screenshot illustrates that isError and isSuccess will consistently differ.

πŸ”Ή error

The error response that comes from the API.

  const {
    error,
  } = useGetAlbumsQuery(page);
Enter fullscreen mode Exit fullscreen mode

This is how I utilize isError and error results.

  if (isError) {
    console.log({ error });
    return <div>{error.status}</div>;
  }
Enter fullscreen mode Exit fullscreen mode

πŸ”Ή refetch

It's an interesting feature, so we can run refect whenever we want. I believe it can be used for refreshing the data. If you need to refresh the data when the user clicks a button. You can utilize this function.

  const {
    refetch,
  } = useGetAlbumsQuery(page);
Enter fullscreen mode Exit fullscreen mode

I created a new button for this refetch feature.

      <button disabled={isError} onClick={refetch}>
        Refetch
      </button>
Enter fullscreen mode Exit fullscreen mode

refetch

πŸ”Ή requested

I assume this attribute is not necessary to be used in your project. However, I want to point out about invalidating the cache. The requestId will be updated to be a different id if RTK query runs a refetch or update the cache.

  const {
    requestId,
  } = useGetAlbumsQuery(page);
Enter fullscreen mode Exit fullscreen mode

requestId
What I did to simulate the GIF

  1. Go to the next page
  2. Back to the previous page
  3. Go to the next page (Same request ID with number 1)
  4. Back to the next page (Same request ID with number 1)
  5. Click Refetch button(new request Id is generated)
  6. Click Refetch button(new request Id is generated)
  7. Click Refetch button(new request Id is generated)

πŸ”Ή The Rest of Result Attributes

I believe that the above attributes that I already explained are the most important attributes that we can implement for real-life scenarios. Other attributes could be useful in your case, but I believe it's easy to understand if you go to the docs.

Conclusion

In summary, learning about the useQuery hook in RTK Query has shown us lots of cool things beyond just getting data. The UseQueryOptions allows us to control how fetching works, like skipping the first fetch, changing the data after getting it, and even fetching again based on time or user actions. The UseQueryResult attributes, like data, isLoading, and isError, help us know what's happening with our fetch in real time, making it easier to build user-friendly apps. Even though some things, like requestId, might seem tricky, they're useful for certain situations, like updating stored data. So, as we finish up, remember that understanding useQuery in RTK Query opens up new possibilities for making efficient React apps. Feel free to explore more in the documentation to see what fits best for your projects! Happy Reading, and leave a comment if you have questions. πŸ“š

Top comments (3)

Collapse
 
batuhan3428 profile image
batu-han3428

After performing a mutation operation in one tab, how can I automatically run the query in the other tab? I used "invalidatesTags" but it didn't work. If I'm on the same tab, "invalidatesTags" works. but it doesn't work in different tabs.

Collapse
 
markerikson profile image
Mark Erikson

Nice! Always happy to see folks actively using RTK Query!

Collapse
 
ignatiusk profile image
Ignatius K

Great work, Raynaldo.
Thanks for this.