Queries#

This is the most basic feature of RTK Query. A query operation can be performed with any data fetching library of your choice, but the general recommendation is that you only use queries for requests that retrieve data. For anything that alters data on the server or will possibly invalidate the cache, you should use a Mutation.

By default, RTK Query ships with fetchBaseQuery, which is a lightweight fetch wrapper that automatically handles request headers and response parsing in a manner similar to common libraries like axios. See Customizing Queries if fetchBaseQuery does not handle your requirements.

Depending on your environment, you may need to polyfill fetch with node-fetch or cross-fetch if you choose to use fetchBaseQuery or fetch on its own.

See useQuery for the hook signature and additional details.

Example of all query endpoint options
// Or from '@reduxjs/toolkit/query/react'
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
import { Post } from './types'
const api = createApi({
baseQuery: fetchBaseQuery({
baseUrl: '/',
}),
tagTypes: ['Post'],
endpoints: (build) => ({
getPost: build.query<Post, number>({
// note: an optional `queryFn` may be used in place of `query`
query: (id) => ({ url: `post/${id}` }),
// Pick out data and prevent nested properties in a hook or selector
transformResponse: (response: { data: Post }) => response.data,
providesTags: (result, error, id) => [{ type: 'Post', id }],
// The 2nd parameter is the destructured `QueryLifecycleApi`
async onQueryStarted(
arg,
{
dispatch,
getState,
extra,
requestId,
queryFulfilled,
getCacheEntry,
updateCachedData,
}
) {},
// The 2nd parameter is the destructured `QueryCacheLifecycleApi`
async onCacheEntryAdded(
arg,
{
dispatch,
getState,
extra,
requestId,
cacheEntryRemoved,
cacheDataLoaded,
getCacheEntry,
updateCachedData,
}
) {},
}),
}),
})

Performing Queries with React Hooks#

If you're using React Hooks, RTK Query does a few additional things for you. The primary benefit is that you get a render-optimized hook that allows you to have 'background fetching' as well as derived booleans for convenience.

Hooks are automatically generated based on the name of the endpoint in the service definition. An endpoint field with getPost: builder.query() will generate a hook named useGetPostQuery.

Hook types#

There are 5 query-related hooks:

  1. useQuery
    • Composes useQuerySubscription and useQueryState and is the primary hook. 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.
  2. useQuerySubscription
    • Returns a refetch function and accepts all hook options. Automatically triggers fetches of data from an endpoint, and 'subscribes' the component to the cached data.
  3. useQueryState
    • Returns the query state and accepts skip and selectFromResult. Reads the request status and cached data from the Redux store.
  4. useLazyQuery
    • Returns a tuple with a fetch function, the query result, and last promise info. Similar to useQuery, but with manual control over when the data fetching occurs.
  5. useLazyQuerySubscription
    • Returns a tuple with a fetch function, and last promise info. Similar to useQuerySubscription, but with manual control over when the data fetching occurs.

Query Hook Options#

  • skip - Allows a query to 'skip' running for that render. Defaults to false
  • pollingInterval - Allows a query to automatically refetch on a provided interval, specified in milliseconds. Defaults to 0 (off)
  • selectFromResult - Allows altering the returned value of the hook to obtain a subset of the result, render-optimized for the returned subset.
  • refetchOnMountOrArgChange - Allows forcing the query to always refetch on mount (when true is provided). Allows forcing the query to refetch if enough time (in seconds) has passed since the last query for the same cache (when a number is provided). Defaults to false
  • refetchOnFocus - Allows forcing the query to refetch when the browser window regains focus. Defaults to false
  • refetchOnReconnect - Allows forcing the query to refetch when regaining a network connection. Defaults to false

All refetch-related options will override the defaults you may have set in createApi

Frequently Used Query Hook Return Values#

The query hook returns an object containing properties such as the latest data for the query request, as well as status booleans for the current request lifecycle state. Below are some of the most frequently used properties. Refer to useQuery for an extensive list of all returned properties.

  • data - The returned result if present.
  • error - The error result if present.
  • isUninitialized - When true, indicates that the query has not started yet.
  • isLoading - When true, indicates that the query is currently loading for the first time, and has no data yet. This will be true for the first request fired off, but not for subsequent requests.
  • isFetching - When true, indicates that the query is currently fetching, but might have data from an earlier request. This will be true for both the first request fired off, as well as subsequent requests.
  • isSuccess - When true, indicates that the query has data from a successful request.
  • isError - When true, indicates that the query is in an error state.
  • refetch - A function to force refetch the query

Example#

Here is an example of a PostDetail component:

Example
export const PostDetail = ({ id }: { id: string }) => {
const { data: post, isFetching, isLoading } = useGetPostQuery(id, {
pollingInterval: 3000,
refetchOnMountOrArgChange: true,
skip: false,
})
if (isLoading) return <div>Loading...</div>
if (!post) return <div>Missing post!</div>
return (
<div>
{post.name} {isFetching ? '...refetching' : ''}
</div>
)
}

The way that this component is setup would have some nice traits:

  1. It only shows 'Loading...' on the initial load

    • Initial load is defined as a query that is pending and does not have data in the cache
  2. When the request is re-triggered by the polling interval, it will add '...refetching' to the post name

  3. If a user closed this PostDetail, but then re-opened it within the allowed time, they would immediately be served a cached result and polling would resume with the previous behavior.

Selecting data from a query result#

Sometimes you may have a parent component that is subscribed to a query, and then in a child component you want to pick an item from that query. In most cases you don't want to perform an additional request for a getItemById-type query when you know that you already have the result.

selectFromResult allows you to get a specific segment from a query result in a performant manner. When using this feature, the component will not rerender unless the underlying data of the selected item has changed. If the selected item is one element in a larger collection, it will disregard changes to elements in the same collection.

Using selectFromResult to extract a single result
function PostsList() {
const { data: posts } = api.useGetPostsQuery()
return (
<ul>
{posts?.data?.map((post) => (
<PostById key={post.id} id={post.id} />
))}
</ul>
)
}
function PostById({ id }: { id: number }) {
// Will select the post with the given id, and will only rerender if the given posts data changes
const { post } = api.useGetPostsQuery(undefined, {
selectFromResult: ({ data }) => ({
post: data?.find((post) => post.id === id),
}),
})
return <li>{post?.name}</li>
}

Query Cache Keys#

When you perform a query, RTK Query automatically serializes the request parameters and creates an internal queryCacheKey for the request. Any future request that produces the same queryCacheKey will be de-duped against the original, and will share updates if a refetch is trigged on the query from any subscribed component.

Avoiding unnecessary requests#

By default, if you add a component that makes the same query as an existing one, no request will be performed.

In some cases, you may want to skip this behavior and force a refetch - in that case, you can call refetch that is returned by the hook.

If you're not using React Hooks, you can access refetch like this:

const { status, data, error, refetch } = dispatch(
pokemonApi.endpoints.getPokemon.initiate('bulbasaur')
)

Observing caching behavior#

What you'll see below is this:

  1. The first Pokemon component mounts and immediately fetches 'bulbasaur'

  2. A second later, another Pokemon component is rendered with 'bulbasaur'

    • Notice that this one doesn't ever show 'Loading...' and no new network request happens? It's using the cache here.
  3. A moment after that, a Pokemon component for 'pikachu' is added, and a new request happens.

  4. When you click 'Refetch' of a particular pokemon type, it'll update all of them with one request.

Try it out

Click the 'Add bulbasaur' button. You'll observe the same behavior described above until you click the 'Refetch' button on one of the components.