skip to content

In RTK Query, which hooks does createApi generate for a getPosts query and an updatePost mutation, and how do isLoading and isFetching differ?

level: juniorimportance: must knowfreq 62%

answer

  1. use + Endpointname + type
  2. a lazy variant for queries
  3. mutations return a tuple
  4. first load versus any request

basics

~20 s

RTK Query's React createApi generates useGetPostsQuery and useLazyGetPostsQuery for the query and useUpdatePostMutation for the mutation. isLoading is true only while the hook has no data yet; isFetching is true whenever a request is in flight, including refetches.

solid answer

~40 s

Created from `@reduxjs/toolkit/query/react`, `createApi` names hooks `use` + capitalised endpoint name + `Query` or `Mutation`: `useGetPostsQuery(arg, options)` subscribes and fetches on mount; `useLazyGetPostsQuery()` returns `[trigger, result, lastPromiseInfo]` and fetches only when you call `trigger`; `useUpdatePostMutation()` returns `[updatePost, result]`. The same hooks live on `api.endpoints.getPosts.useQuery`. In a query result, `isLoading` means **in flight with no data yet** — the first load; `isFetching` means **any request in flight**, including a refetch or an argument change. Because `data` keeps the latest result regardless of the argument, switching from page 1 to page 2 shows `isLoading: false`, `isFetching: true` and page 1's `data`, while `currentData` is `undefined` until page 2 arrives.

code

ts · 28 lines
ts
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';

export type Post = { id: number; title: string; body: string };

export const api = createApi({
  reducerPath: 'api',
  baseQuery: fetchBaseQuery({ baseUrl: '/api' }),
  tagTypes: ['Post'],
  endpoints: (build) => ({
    getPosts: build.query<Post[], void>({
      query: () => 'posts',
    }),
    getPost: build.query<Post, number>({
      query: (id) => `posts/${id}`,
    }),
    updatePost: build.mutation<Post, Pick<Post, 'id'> & Partial<Post>>({
      query: ({ id, ...patch }) => ({ url: `posts/${id}`, method: 'PATCH', body: patch }),
    }),
  }),
});

// Generated hooks: use + Endpointname + Query | Mutation
export const {
  useGetPostsQuery,
  useLazyGetPostsQuery,
  useGetPostQuery,
  useUpdatePostMutation,
} = api;

go deeper

for a junior

Recall the naming rule for generated hooks and the difference between isLoading and isFetching.

for a middle

Explain data versus currentData on an argument change, lazy queries, skipToken and unwrap on mutations.

for a senior

Design loading states that never blank the UI on refetch, and pick data or currentData per screen deliberately.

for a principal

Set conventions for loading and error states across screens built on generated hooks so the product behaves consistently.

## What createApi generates RTK Query is the data-fetching layer shipped inside Redux Toolkit 2. You describe endpoints once with `createApi`, and the React build of it (imported from `@reduxjs/toolkit/query/react`) generates hooks for each one. The naming rule is fixed: > `use` + endpoint name with its first letter capitalised + `Query` | `Mutation` | `InfiniteQuery` | Endpoint definition | Generated on the api object | What it returns | |---|---|---| | `getPosts: build.query(...)` | `useGetPostsQuery` | Result object: `data`, `error`, `isLoading`, `isFetching`, `refetch`... | | same | `useLazyGetPostsQuery` | `[trigger, result, lastPromiseInfo]` | | `updatePost: build.mutation(...)` | `useUpdatePostMutation` | `[updatePost, result]` | The same hooks exist under generic names on the endpoint objects — `api.endpoints.getPosts.useQuery()` — together with a few that are generated only there, such as `useQueryState` and `useQuerySubscription`. ## Query hook versus lazy query hook - **`useGetPostsQuery(arg, options)`** starts a subscription when the component mounts and fetches unless the data is already cached. Pass `skip: true` or the `skipToken` value as the argument to hold it back. - **`useLazyGetPostsQuery()`** does nothing until you call `trigger(arg)`, which suits search-on-submit or a "load details" button. ## Mutation hook `const [updatePost, { isLoading, error }] = useUpdatePostMutation()` gives you a trigger and the state of the latest call. `updatePost(changes)` returns a promise-like object; `.unwrap()` on it resolves with the data or throws the error, which is the idiomatic way to `await` it in a handler. ## isLoading versus isFetching The generated query hooks expose derived booleans rather than one `status` string, because several can be true at once: - **`isUninitialized`** — the query has not started (for example, while skipped). - **`isLoading`** — a request is in flight **and the hook has no data yet**: the first load. - **`isFetching`** — **any** request is in flight: the first load, a refetch, a tag-triggered refetch, or a new argument. - **`isSuccess`** — there is data from a successful request. - **`isError`** — the latest request failed. So `isLoading` implies `isFetching`, but not the other way round. The documented use is a skeleton on `isLoading` and a dimmed list or small spinner on `isFetching`. ## data versus currentData on an argument change 1. The component renders `useGetPostsQuery(page)` with `page = 1`; page 1 loads. 2. The user clicks "next", so `page = 2`. 3. While page 2 loads: `isLoading` is `false` (the hook already has data), `isFetching` is `true`, `data` is still **page 1** — the latest result regardless of argument — and `currentData` is `undefined`, because it tracks only the current argument. Show `data` to keep the old page visible while the next loads; show `currentData` when stale content for a different argument would mislead. ## Common mistakes - **Showing a full-page spinner on `isFetching`**, so every background refetch blanks the screen. - **Expecting `useLazy...Query` to fetch on mount.** - **Hand-writing hook names**; the generated names must match the endpoint key exactly. - **Importing `createApi` from `@reduxjs/toolkit/query`** (the UI-agnostic entry point) and then looking for hooks: only the `/react` entry point generates them.

  • In RTK Query, how do you stop useGetPostQuery(id) from running until an id is known?
    Pass `skipToken` as the argument — `useGetPostQuery(id ?? skipToken)` — or `{ skip: !id }` in the options. The hook stays `isUninitialized` and sends nothing; once a real argument arrives, it subscribes and fetches. `skipToken` also keeps the TypeScript argument type honest.
  • How do you await the result of an RTK Query mutation in an event handler?
    Call `await updatePost(changes).unwrap()`. `unwrap()` resolves with the response data or throws the error, so a `try/catch` around it handles failure. Without `unwrap`, the returned promise resolves to an object with either `data` or `error` and never throws.
  • When would you show currentData instead of data from an RTK Query hook?
    When showing the previous argument's result would mislead — for example, a detail panel that must never show user A's profile while user B's loads. `currentData` is `undefined` until the current argument's data arrives, while `data` keeps the latest result from any argument.

saying these in an interview costs you the question

  • isLoading is true during every refetch
  • useLazyGetPostsQuery fetches as soon as the component mounts
  • Changing the hook argument clears data until the new response arrives
  • The mutation hook returns an object with a mutate function
  • createApi from '@reduxjs/toolkit/query' also generates React hooks