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.
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);
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,
});
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)
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,
})
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,
});
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,
});
In the next GIF, I added a new state called skip
, so I can modify it in react dev tools.
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.
🔸 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;
},
});
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 });
🔸 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,
});
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,
});
🔸 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,
});
🔸 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/
🔹 data
It's the result of the fetch data.
const {
data: album = [],
} = useGetAlbumsQuery(page);
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) => ( ... ))}
🔹 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);
🔹 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);
If you remember some options that I explained before, such as refetchOnFocus
, refetchOnReconnect
, and pollingInterval
. It will affect the isFetching
, not the isLoading
This is why I handle loading with these two statuses.
if (isLoading || isFetching) {
return <div>loading...</div>;
}
🔹 isSuccess
Status for showing the fetching or refetching is successful. it is opposite of isError
status.
const {
isSuccess,
} = useGetAlbumsQuery(page);
🔹 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);
The screenshot illustrates that isError and isSuccess will consistently differ.
🔹 error
The error response that comes from the API.
const {
error,
} = useGetAlbumsQuery(page);
This is how I utilize isError
and error
results.
if (isError) {
console.log({ error });
return <div>{error.status}</div>;
}
🔹 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);
I created a new button for this refetch feature.
<button disabled={isError} onClick={refetch}>
Refetch
</button>
🔹 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);
What I did to simulate the GIF
- Go to the next page
- Back to the previous page
- Go to the next page (Same request ID with number 1)
- Back to the next page (Same request ID with number 1)
- Click Refetch button(new request Id is generated)
- Click Refetch button(new request Id is generated)
- 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)
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.
Nice! Always happy to see folks actively using RTK Query!
Great work, Raynaldo.
Thanks for this.