Skip to content

Latest commit

 

History

History
696 lines (535 loc) · 24.1 KB

createApi.mdx

File metadata and controls

696 lines (535 loc) · 24.1 KB
id title sidebar_label hide_title description
createApi
createApi
createApi
true
RTK Query > API: createApi reference

 

createApi

createApi is the core of RTK Query's functionality. It allows you to define a set of "endpoints" that describe how to retrieve data from backend APIs and other async sources, including the configuration of how to fetch and transform that data. It generates an "API slice" structure that contains Redux logic (and optionally React hooks) that encapsulate the data fetching and caching process for you.

:::tip

Typically, you should only have one API slice per base URL that your application needs to communicate with. For example, if your site fetches data from both /api/posts and /api/users, you would have a single API slice with /api/ as the base URL, and separate endpoint definitions for posts and users. This allows you to effectively take advantage of automated re-fetching by defining tag relationships across endpoints.

For maintainability purposes, you may wish to split up endpoint definitions across multiple files, while still maintaining a single API slice which includes all of these endpoints. See code splitting for how you can use the injectEndpoints property to inject API endpoints from other files into a single API slice definition.

:::

// file: src/services/types.ts noEmit
export type Pokemon = {}

// file: src/services/pokemon.ts
// Need to use the React-specific entry point to allow generating React hooks
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
import type { Pokemon } from './types'

// highlight-start
// Define a service using a base URL and expected endpoints
export const pokemonApi = createApi({
  reducerPath: 'pokemonApi',
  baseQuery: fetchBaseQuery({ baseUrl: 'https://pokeapi.co/api/v2/' }),
  endpoints: (builder) => ({
    getPokemonByName: builder.query<Pokemon, string>({
      query: (name) => `pokemon/${name}`,
    }),
  }),
})
//highlight-end

// highlight-start
// Export hooks for usage in function components, which are
// auto-generated based on the defined endpoints
export const { useGetPokemonByNameQuery } = pokemonApi
// highlight-end

Parameters

createApi accepts a single configuration object parameter with the following options:

  baseQuery(args: InternalQueryArgs, api: BaseQueryApi, extraOptions?: DefinitionExtraOptions): any;
  endpoints(build: EndpointBuilder<InternalQueryArgs, TagTypes>): Definitions;
  extractRehydrationInfo?: (
    action: UnknownAction,
    {
      reducerPath,
    }: {
      reducerPath: ReducerPath
    }
  ) =>
    | undefined
    | CombinedState<Definitions, TagTypes, ReducerPath>
  tagTypes?: readonly TagTypes[];
  reducerPath?: ReducerPath;
  serializeQueryArgs?: SerializeQueryArgs<InternalQueryArgs>;
  keepUnusedDataFor?: number; // value is in seconds
  refetchOnMountOrArgChange?: boolean | number; // value is in seconds
  refetchOnFocus?: boolean;
  refetchOnReconnect?: boolean;

baseQuery

summary

baseQuery function arguments

  • args - The return value of the query function for a given endpoint
  • api - The BaseQueryApi object contains:
    • signal - An AbortSignal object that may be used to abort DOM requests and/or read whether the request is aborted.
    • abort - The abort() method of the AbortController attached to signal.
    • dispatch - The store.dispatch method for the corresponding Redux store
    • getState - A function that may be called to access the current store state
    • extra - Provided as thunk.extraArgument to the configureStore getDefaultMiddleware option.
    • endpoint - The name of the endpoint.
    • type - Type of request (query or mutation).
    • forced - Indicates if a query has been forced.
  • extraOptions - The value of the optional extraOptions property provided for a given endpoint

baseQuery function signature

export type BaseQueryFn<
  Args = any,
  Result = unknown,
  Error = unknown,
  DefinitionExtraOptions = {},
  Meta = {},
> = (
  args: Args,
  api: BaseQueryApi,
  extraOptions: DefinitionExtraOptions,
) => MaybePromise<QueryReturnValue<Result, Error, Meta>>

export interface BaseQueryApi {
  signal: AbortSignal
  abort: (reason?: string) => void
  dispatch: ThunkDispatch<any, any, any>
  getState: () => unknown
  extra: unknown
  endpoint: string
  type: 'query' | 'mutation'
  forced?: boolean
}

export type QueryReturnValue<T = unknown, E = unknown, M = unknown> =
  | {
      error: E
      data?: undefined
      meta?: M
    }
  | {
      error?: undefined
      data: T
      meta?: M
    }

examples

endpoints

summary

See Anatomy of an endpoint for details on individual properties.

Query endpoint definition

export type QueryDefinition<
  QueryArg,
  BaseQuery extends BaseQueryFn,
  TagTypes extends string,
  ResultType,
  ReducerPath extends string = string,
> = {
  query(arg: QueryArg): BaseQueryArg<BaseQuery>

  /* either `query` or `queryFn` can be present, but not both simultaneously */
  queryFn(
    arg: QueryArg,
    api: BaseQueryApi,
    extraOptions: BaseQueryExtraOptions<BaseQuery>,
    baseQuery: (arg: Parameters<BaseQuery>[0]) => ReturnType<BaseQuery>,
  ): MaybePromise<QueryReturnValue<ResultType, BaseQueryError<BaseQuery>>>

  /* transformResponse only available with `query`, not `queryFn` */
  transformResponse?(
    baseQueryReturnValue: BaseQueryResult<BaseQuery>,
    meta: BaseQueryMeta<BaseQuery>,
    arg: QueryArg,
  ): ResultType | Promise<ResultType>

  /* transformErrorResponse only available with `query`, not `queryFn` */
  transformErrorResponse?(
    baseQueryReturnValue: BaseQueryError<BaseQuery>,
    meta: BaseQueryMeta<BaseQuery>,
    arg: QueryArg,
  ): unknown

  extraOptions?: BaseQueryExtraOptions<BaseQuery>

  providesTags?: ResultDescription<
    TagTypes,
    ResultType,
    QueryArg,
    BaseQueryError<BaseQuery>
  >

  keepUnusedDataFor?: number

  onQueryStarted?(
    arg: QueryArg,
    {
      dispatch,
      getState,
      extra,
      requestId,
      queryFulfilled,
      getCacheEntry,
      updateCachedData, // available for query endpoints only
    }: QueryLifecycleApi,
  ): Promise<void>

  onCacheEntryAdded?(
    arg: QueryArg,
    {
      dispatch,
      getState,
      extra,
      requestId,
      cacheEntryRemoved,
      cacheDataLoaded,
      getCacheEntry,
      updateCachedData, // available for query endpoints only
    }: QueryCacheLifecycleApi,
  ): Promise<void>
}

Mutation endpoint definition

export type MutationDefinition<
  QueryArg,
  BaseQuery extends BaseQueryFn,
  TagTypes extends string,
  ResultType,
  ReducerPath extends string = string,
  Context = Record<string, any>,
> = {
  query(arg: QueryArg): BaseQueryArg<BaseQuery>

  /* either `query` or `queryFn` can be present, but not both simultaneously */
  queryFn(
    arg: QueryArg,
    api: BaseQueryApi,
    extraOptions: BaseQueryExtraOptions<BaseQuery>,
    baseQuery: (arg: Parameters<BaseQuery>[0]) => ReturnType<BaseQuery>,
  ): MaybePromise<QueryReturnValue<ResultType, BaseQueryError<BaseQuery>>>

  /* transformResponse only available with `query`, not `queryFn` */
  transformResponse?(
    baseQueryReturnValue: BaseQueryResult<BaseQuery>,
    meta: BaseQueryMeta<BaseQuery>,
    arg: QueryArg,
  ): ResultType | Promise<ResultType>

  /* transformErrorResponse only available with `query`, not `queryFn` */
  transformErrorResponse?(
    baseQueryReturnValue: BaseQueryError<BaseQuery>,
    meta: BaseQueryMeta<BaseQuery>,
    arg: QueryArg,
  ): unknown

  extraOptions?: BaseQueryExtraOptions<BaseQuery>

  invalidatesTags?: ResultDescription<TagTypes, ResultType, QueryArg>

  onQueryStarted?(
    arg: QueryArg,
    {
      dispatch,
      getState,
      extra,
      requestId,
      queryFulfilled,
      getCacheEntry,
    }: MutationLifecycleApi,
  ): Promise<void>

  onCacheEntryAdded?(
    arg: QueryArg,
    {
      dispatch,
      getState,
      extra,
      requestId,
      cacheEntryRemoved,
      cacheDataLoaded,
      getCacheEntry,
    }: MutationCacheLifecycleApi,
  ): Promise<void>
}

How endpoints get used

When defining a key like getPosts as shown below, it's important to know that this name will become exportable from api and be able to referenced under api.endpoints.getPosts.useQuery(), api.endpoints.getPosts.initiate() and api.endpoints.getPosts.select(). The same thing applies to mutations but they reference useMutation instead of useQuery.

import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
interface Post {
  id: number
  name: string
}
type PostsResponse = Post[]

const api = createApi({
  baseQuery: fetchBaseQuery({ baseUrl: '/' }),
  tagTypes: ['Posts'],
  endpoints: (build) => ({
    getPosts: build.query<PostsResponse, void>({
      query: () => 'posts',
      providesTags: (result) =>
        result ? result.map(({ id }) => ({ type: 'Posts', id })) : [],
    }),
    addPost: build.mutation<Post, Partial<Post>>({
      query: (body) => ({
        url: `posts`,
        method: 'POST',
        body,
      }),
      invalidatesTags: ['Posts'],
    }),
  }),
})

// Auto-generated hooks
export const { useGetPostsQuery, useAddPostMutation } = api

// Possible exports
export const { endpoints, reducerPath, reducer, middleware } = api
// reducerPath, reducer, middleware are only used in store configuration
// endpoints will have:
// endpoints.getPosts.initiate(), endpoints.getPosts.select(), endpoints.getPosts.useQuery()
// endpoints.addPost.initiate(), endpoints.addPost.select(), endpoints.addPost.useMutation()
// see `createApi` overview for _all exports_

extractRehydrationInfo

summary

examples

See also Server Side Rendering and Persistence and Rehydration.

tagTypes

summary

examples

reducerPath

summary

examples

serializeQueryArgs

summary

By default, this function will take the query arguments, sort object keys where applicable, stringify the result, and concatenate it with the endpoint name. This creates a cache key based on the combination of arguments + endpoint name (ignoring object key order), such that calling any given endpoint with the same arguments will result in the same cache key.

keepUnusedDataFor

summary

refetchOnMountOrArgChange

summary

:::note You can set this globally in createApi, but you can also override the default value and have more granular control by passing refetchOnMountOrArgChange to each individual hook call or similarly by passing forceRefetch: true when dispatching the initiate action. :::

refetchOnFocus

summary

:::note You can set this globally in createApi, but you can also override the default value and have more granular control by passing refetchOnFocus to each individual hook call or when dispatching the initiate action.

If you specify track: false when manually dispatching queries, RTK Query will not be able to automatically refetch for you. :::

refetchOnReconnect

summary

:::note You can set this globally in createApi, but you can also override the default value and have more granular control by passing refetchOnReconnect to each individual hook call or when dispatching the initiate action.

If you specify track: false when manually dispatching queries, RTK Query will not be able to automatically refetch for you. :::

Anatomy of an endpoint

query

(required if no queryFn provided)

export type query = <QueryArg>(
  arg: QueryArg,
) => string | Record<string, unknown>

// with `fetchBaseQuery`
export type query = <QueryArg>(arg: QueryArg) => string | FetchArgs

summary

examples

queryFn

(required if no query provided)

summary

Called with the same arguments as baseQuery, as well as the provided baseQuery function itself. It is expected to return an object with either a data or error property, or a promise that resolves to return such an object.

See also Customizing queries with queryFn.

queryFn(
  arg: QueryArg,
  api: BaseQueryApi,
  extraOptions: BaseQueryExtraOptions<BaseQuery>,
  baseQuery: (arg: Parameters<BaseQuery>[0]) => ReturnType<BaseQuery>
): MaybePromise<
| {
    error: BaseQueryError<BaseQuery>
    data?: undefined
  }
| {
    error?: undefined
    data: ResultType
  }
>

export interface BaseQueryApi {
  signal: AbortSignal
  dispatch: ThunkDispatch<any, any, any>
  getState: () => unknown
}

queryFn function arguments

  • args - The argument provided when the query itself is called
  • api - The BaseQueryApi object, containing signal, dispatch and getState properties
    • signal - An AbortSignal object that may be used to abort DOM requests and/or read whether the request is aborted.
    • dispatch - The store.dispatch method for the corresponding Redux store
    • getState - A function that may be called to access the current store state
  • extraOptions - The value of the optional extraOptions property provided for the endpoint
  • baseQuery - The baseQuery function provided to the api itself

examples

transformResponse

(optional, not applicable with queryFn)

summary

In some cases, you may want to manipulate the data returned from a query before you put it in the cache. In this instance, you can take advantage of transformResponse.

See also Customizing query responses with transformResponse

transformResponse: (response, meta, arg) =>
  response.some.deeply.nested.collection

transformErrorResponse

(optional, not applicable with queryFn)

summary

In some cases, you may want to manipulate the error returned from a query before you put it in the cache. In this instance, you can take advantage of transformErrorResponse.

See also Customizing query responses with transformErrorResponse

transformErrorResponse: (response, meta, arg) =>
  response.data.some.deeply.nested.errorObject

extraOptions

(optional)

Passed as the third argument to the supplied baseQuery function

providesTags

(optional, only for query endpoints)

summary

See also Providing cache data.

examples

invalidatesTags

(optional, only for mutation endpoints)

summary

See also Invalidating cache data.

examples

keepUnusedDataFor

(optional, only for query endpoints)

Overrides the api-wide definition of keepUnusedDataFor for this endpoint only.

summary

examples

serializeQueryArgs

(optional, only for query endpoints)

summary

examples

merge

(optional, only for query endpoints)

summary

examples

forceRefetch

(optional, only for query endpoints)

type forceRefetch = (params: {
  currentArg: QueryArg | undefined
  previousArg: QueryArg | undefined
  state: RootState<any, any, string>
  endpointState?: QuerySubState<any>
}) => boolean

summary

examples

onQueryStarted

(optional)

Available to both queries and mutations.

A function that is called when you start each individual query or mutation. The function is called with a lifecycle api object containing properties such as queryFulfilled, allowing code to be run when a query is started, when it succeeds, and when it fails (i.e. throughout the lifecycle of an individual query/mutation call).

Can be used in mutations for optimistic updates.

Lifecycle API properties

  • dispatch - The dispatch method for the store.
  • getState - A method to get the current state for the store.
  • extra - extra as provided as thunk.extraArgument to the configureStore getDefaultMiddleware option.
  • requestId - A unique ID generated for the query/mutation.
  • queryFulfilled - A Promise that will resolve with a data property (the transformed query result), and a meta property (meta returned by the baseQuery). If the query fails, this Promise will reject with the error. This allows you to await for the query to finish.
  • getCacheEntry - A function that gets the current value of the cache entry.
  • updateCachedData (query endpoints only) - A function that accepts a 'recipe' callback specifying how to update the data for the corresponding cache at the time it is called. This uses immer internally, and updates can be written 'mutably' while safely producing the next immutable state.
async function onQueryStarted(
  arg: QueryArg,
  {
    dispatch,
    getState,
    extra,
    requestId,
    queryFulfilled,
    getCacheEntry,
  }: MutationLifecycleApi,
): Promise<void>
async function onQueryStarted(
  arg: QueryArg,
  {
    dispatch,
    getState,
    extra,
    requestId,
    queryFulfilled,
    getCacheEntry,
    updateCachedData, // available for query endpoints only
  }: QueryLifecycleApi,
): Promise<void>
// file: notificationsSlice.ts noEmit
export const messageCreated = (msg: string) => ({
  type: 'notifications/messageCreated',
  payload: msg,
})

// file: api.ts
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
import { messageCreated } from './notificationsSlice'

export interface Post {
  id: number
  name: string
}

const api = createApi({
  baseQuery: fetchBaseQuery({
    baseUrl: '/',
  }),
  endpoints: (build) => ({
    getPost: build.query<Post, number>({
      query: (id) => `post/${id}`,
      async onQueryStarted(id, { dispatch, queryFulfilled }) {
        // `onStart` side-effect
        dispatch(messageCreated('Fetching post...'))
        try {
          const { data } = await queryFulfilled
          // `onSuccess` side-effect
          dispatch(messageCreated('Post received!'))
        } catch (err) {
          // `onError` side-effect
          dispatch(messageCreated('Error fetching post!'))
        }
      },
    }),
  }),
})

onCacheEntryAdded

(optional)

Available to both queries and mutations.

A function that is called when a new cache entry is added, i.e. when a new subscription for the endpoint + query parameters combination is created. The function is called with a lifecycle api object containing properties such as cacheDataLoaded & cacheDataRemoved, allowing code to be run when a cache entry is added, when cache data is loaded, and when the cache entry is removed (i.e. throughout the lifecycle of a cache entry).

Can be used for streaming updates.

Cache Lifecycle API properties

  • dispatch - The dispatch method for the store.
  • getState - A method to get the current state for the store.
  • extra - extra as provided as thunk.extraArgument to the configureStore getDefaultMiddleware option.
  • requestId - A unique ID generated for the cache entry.
  • cacheEntryRemoved - A Promise that allows you to wait for the point in time when the cache entry has been removed from the cache, by not being used/subscribed to any more in the application for too long or by dispatching api.util.resetApiState.
  • cacheDataLoaded - A Promise that will resolve with the first value for this cache key. This allows you to await until an actual value is in the cache.
    Note: If the cache entry is removed from the cache before any value has ever been resolved, this Promise will reject with new Error('Promise never resolved before cacheEntryRemoved.') to prevent memory leaks. You can just re-throw that error (or not handle it at all) - it will be caught outside of cacheEntryAdded.
  • getCacheEntry - A function that gets the current value of the cache entry.
  • updateCachedData (query endpoints only) - A function that accepts a 'recipe' callback specifying how to update the data at the time it is called. This uses immer internally, and updates can be written 'mutably' while safely producing the next immutable state.
async function onCacheEntryAdded(
  arg: QueryArg,
  {
    dispatch,
    getState,
    extra,
    requestId,
    cacheEntryRemoved,
    cacheDataLoaded,
    getCacheEntry,
  }: MutationCacheLifecycleApi,
): Promise<void>
async function onCacheEntryAdded(
  arg: QueryArg,
  {
    dispatch,
    getState,
    extra,
    requestId,
    cacheEntryRemoved,
    cacheDataLoaded,
    getCacheEntry,
    updateCachedData, // available for query endpoints only
  }: QueryCacheLifecycleApi,
): Promise<void>

Return value

See the "created Api" API reference