Data Cache in RTK Query
Default Cache Behavior
The default cache behavior of RTK query is awesome for performance. When a request/subscription is started, the parameters used in the endpoints are serialized and stored internally as a queryCacheKey for the request. If a next request is made with the same queryCacheKey, it will
be deduped against the original and will share the same data and updates. In short, if two separate component is performing the same request, the first one will make the actual Api call and the second one will get the data from cache.
If the parameter is different, the result should be different so it will make a new request. But as long as the parameter remains same, it will return the same result from the cache.
Request is made
-> data exist in cache : fetch from the cache
-> data no exist in cache : make the api request and fetch data AND save it in cache
The cache default time is 60 sec. After this time, the cache data is removed and a new request has to be made. Again this cache data will be valid for 60sec.
If a new request is made in the 40th sec, it gets data from the cache and the timer will reset, hence the cache data will be valid for next 60 sec.
In other word, Data is kept in the cache as long as at least one active subscriber is interested in that endpoint + parameter combination. If there is no new subscription to the cache data by the time, the timer of 60 sec expires, the cached data will be removed.
You can increase the cache time. It can be configured for the whole API or just for a particular endpoint.
Custom cache Behavior
Updating the cache time
The cache time can be configured using the keepUnusedDataFor option for both API definition and for each endpoint. The keepUnusedDataFor if used will override the config provided in the API.
In the example below we can see the keepUnusedDataFor configured for the whole API and for a specific endpoint.
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
import type { Post } from './types'
export const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: '/' }),
// global configuration for the api
keepUnusedDataFor: 30,
endpoints: (builder) => ({
getPosts: builder.query<Post[], number>({
query: () => `posts`,
// configuration for an individual endpoint, overriding the api setting
keepUnusedDataFor: 5,
}),
}),
})
Re-fetching on demand with refetch
/initiate
The useQuery hook exposes the refetch function which we can use to refetch the data on some user action such as button click. This is manual way to refetch data.
As an alternative, you can dispatch initiate
thunk action for an endpoint , passing the option forceRefetch:true
for the same effect. Again this can be triggered on user interaction such as button click.
import { useDispatch } from 'react-redux'
import { useGetPostsQuery } from './api'
const Component = () => {
const dispatch = useDispatch()
const { data, refetch } = useGetPostsQuery({ count: 5 })
function handleRefetchOne() {
// force re-fetches the data
refetch()
}
function handleRefetchTwo() {
// has the same effect as `refetch` for the associated query
dispatch(
api.endpoints.getPosts.initiate(
{ count: 5 },
{ subscribe: false, forceRefetch: true }
)
)
}
return (
<div>
<button onClick={handleRefetchOne}>Force re-fetch 1</button>
<button onClick={handleRefetchTwo}>Force re-fetch 2</button>
</div>
)
}
Re-fetching with refetchOnMountOrArgChange
This property accepts either a boolean value or number (time in sec).
This too can be configured for the whole API or just for some endpoints.
Passing true will make the endpoint always refetch when a new subscriber to the query is added or the argument changes. It will always refetch regardless of whether a cached data for the endpoint + arg combination already exist.
Passing a number to this property is a bit different.
-> When the query subscription is created and there already exist a query in the cache, it will compare the current time vs the last fulfilled time for that query.
If the time provided in sec has passed : refetch
If the time provided in sec has not passed: serve the cached data
-> if there is no query : fetch the data
//Configuring re-fetching on subscription if data exceeds a given time
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
import type { Post } from './types'
export const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: '/' }),
// global configuration for the api
refetchOnMountOrArgChange: 30,
endpoints: (builder) => ({
getPosts: builder.query<Post[], number>({
query: () => `posts`,
}),
}),
})
//Forcing refetch on component mount
import { useGetPostsQuery } from './api'
const Component = () => {
const { data } = useGetPostsQuery(
{ count: 5 },
// this overrules the api definition setting,
// forcing the query to always fetch when this component is mounted
{ refetchOnMountOrArgChange: true }
)
return <div>...</div>
}
Re-fetching on network reconnection with refetchOnReconnect
Useful when you want to refetch data after regaining the network connection.
Note that this requires setupListeners
to have been called.
This option is again available to both API or for a specific endpoint
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
import type { Post } from './types'
export const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: '/' }),
// global configuration for the api
refetchOnReconnect: true,
endpoints: (builder) => ({
getPosts: builder.query<Post[], number>({
query: () => `posts`,
}),
}),
})
import { configureStore } from '@reduxjs/toolkit'
import { setupListeners } from '@reduxjs/toolkit/query'
import { api } from './services/api'
export const store = configureStore({
reducer: {
[api.reducerPath]: api.reducer,
},
middleware: (gDM) => gDM().concat(api.middleware),
})
// enable listener behavior for the store
setupListeners(store.dispatch)
export type RootState = ReturnType<typeof store.getState>
Cache Tags : Re-fetching after mutations by invalidating cache tags
This is useful when you want to reflect the change made to data right away.
Say you have a tabular data and add/edit in the same page. When you add/edit the data, you need it to reflect in the table without page refresh.
Using the cache tags, we can automate the data refetch for query endpoints affected by the mutation endpoint. When a specific mutation endpoint is fired it will cause certain endpoint to consider it's cached data to be invalid and re-fetch the data if there is an active subscription.
For this to work, we''ll have to use the same tag name in both the mutation endpoint and the other(s) endpoint that is affected by the mutation.
export const portfolioApi = createApi({
reducerPath: 'portfolioApi',
keepUnusedDataFor: 60,
tagTypes:["Portfolio"],
baseQuery: fetchBaseQuery({baseUrl: `http://localhost:3030`,}),
endpoints: (builder) => ({
getPortfolio: builder.query<IPortfolio[], void>({
query: () => ({
url: 'portfolio',
method: 'GET',
}),
providesTags: ["Portfolio"]
}),
createPortfolio: builder.mutation<IPortfolio, IPortfolio>({
query: (data) =>({
url: 'portfolio',
method:'POST',
body:data,
}),
invalidatesTags: ["Portfolio"]
})
}),
});
Here in the example, the createPortfolio mutation endpoint will invalidate the data cached by the other endpoint that has the same tag as in the mutation query.
The mutation will use key invalidateTags
whose value is an array of tags that it will invalidate. Now any other endpoint that has the same tag defined in the providesTags
will have its cached data invalid and require refetch.
Here in our example, both the invalidateTags
andprovidesTags
have the same tag "Portfolio" hence calling the createPortfolio mutation endpoint will force getPortfolio endpoint to run the API again and fetch fresh data.
Remember to register the tags in the createApi
first before using them in endpoints.
You can register the tags as
tagTypes:["Portfolio"]