本文へスキップ

クエリのカスタマイズ

RTK Queryは、リクエストの解決方法に依存しません。リクエストの処理には任意のライブラリを使用できますし、ライブラリを使用しないこともできます。RTK Queryは、ほとんどのユースケースをカバーする妥当なデフォルトを提供しながら、特定のニーズに合わせてクエリの処理を変更するためのカスタマイズの余地も残しています。

baseQueryによるクエリのカスタマイズ

クエリを処理するデフォルトの方法は、createApibaseQueryオプションと、エンドポイント定義のqueryオプションを組み合わせて使用することです。

クエリを処理するために、エンドポイントはqueryオプションで定義され、その戻り値はAPIで使用される共通のbaseQuery関数に渡されます。

デフォルトでは、RTK QueryにはfetchBaseQueryが付属しています。これは軽量なfetchラッパーで、axiosなどの一般的なライブラリと同様に、リクエストヘッダーとレスポンスの解析を自動的に処理します。fetchBaseQueryだけではニーズを満たせない場合は、ラッパー関数で動作をカスタマイズするか、baseQuery関数をゼロから作成してcreateApiで使用できます。

こちらも参照してください baseQuery APIリファレンス.

カスタムbaseQueryの実装

RTK Queryでは、baseQuery関数が3つの引数(argsapiextraOptions)で呼び出されることが期待されます。これは、dataプロパティまたはerrorプロパティを持つオブジェクト、あるいはそのようなオブジェクトを返すpromiseを返す必要があります。

ヒント

ベースクエリ関数とクエリ関数は、常に自身でエラーをキャッチし、オブジェクトで返す必要があります!

function brokenCustomBaseQuery() {
// ❌ Don't let this throw by itself
const data = await fetchSomeData()
return { data }
}

function correctCustomBaseQuery() {
// ✅ Catch errors and _return_ them so the RTKQ logic can track it
try {
const data = await fetchSomeData()
return { data }
} catch (error) {
return { error }
}
}

baseQuery関数の引数

baseQuery関数の引数の例
const customBaseQuery = (
args,
{ signal, dispatch, getState },
extraOptions,
) => {
// omitted
}

baseQuery関数の戻り値

  1. 成功時の期待される結果形式
    return { data: YourData }
  2. エラー時の期待される結果形式
    return { error: YourError }
baseQuery関数の戻り値の例
const customBaseQuery = (
args,
{ signal, dispatch, getState },
extraOptions,
) => {
if (Math.random() > 0.5) return { error: 'Too high!' }
return { data: 'All good!' }
}
注記

この形式は、RTK Queryがレスポンスの戻り値の型を推論するために必要です。

基本的に、baseQuery関数は、dataまたはerrorプロパティを持つオブジェクトという最小限の戻り値のみが必要で有効です。提供された引数の使用方法と、関数内でのリクエストの処理方法は、ユーザーが決定します。

fetchBaseQueryのデフォルト値

fetchBaseQueryについては、戻り値の型は次のとおりです。

fetchBaseQueryの戻り値の型
Promise<
| {
data: any
error?: undefined
meta?: { request: Request; response: Response }
}
| {
error: {
status: number
data: any
}
data?: undefined
meta?: { request: Request; response: Response }
}
>
  1. fetchBaseQueryを使用した成功時の期待される結果形式
    return { data: YourData }
  2. fetchBaseQueryを使用したエラー時の期待される結果形式
    return { error: { status: number, data: YourErrorData } }

transformResponseによるクエリレスポンスのカスタマイズ

createApiの個々のエンドポイントは、transformResponseプロパティを受け入れます。これにより、クエリまたはミューテーションによって返されたデータがキャッシュに保存される前に操作できます。

transformResponseは、対応するエンドポイントに対して成功したbaseQueryが返すデータで呼び出され、transformResponseの戻り値はそのエンドポイント呼び出しに関連付けられたキャッシュデータとして使用されます。

デフォルトでは、サーバーからのペイロードが直接返されます。

function defaultTransformResponse(
baseQueryReturnValue: unknown,
meta: unknown,
arg: unknown
) {
return baseQueryReturnValue
}

変更するには、次のような関数を使用します。

深くネストされたコレクションの展開
transformResponse: (response, meta, arg) =>
response.some.deeply.nested.collection

transformResponseは、変換されたレスポンスを決定する際に使用できる、baseQueryから返されたmetaプロパティを2番目の引数として呼び出されます。metaの値は、使用されているbaseQueryによって異なります。

transformResponseのmetaの例
transformResponse: (response: { sideA: Tracks; sideB: Tracks }, meta, arg) => {
if (meta?.coinFlip === 'heads') {
return response.sideA
}
return response.sideB
}

transformResponseは、変換されたレスポンスを決定する際に使用できる、エンドポイントに提供されたargプロパティを3番目の引数として呼び出されます。argの値は、使用されているendpointと、クエリ/ミューテーション呼び出しで使用される引数によって異なります。

transformResponseのargの例
transformResponse: (response: Posts, meta, arg) => {
return {
originalArg: arg,
data: response,
}
}

RTK Queryがキャッシュデータを管理しているので、レスポンスを正規化されたルックアップテーブルに保存する必要性は少なくなっていますが、必要であればtransformResponseを利用して行うことができます。

レスポンスデータの正規化
transformResponse: (response) =>
response.reduce((acc, curr) => {
acc[curr.id] = curr
return acc
}, {})

/*
will convert:
[
{id: 1, name: 'Harry'},
{id: 2, name: 'Ron'},
{id: 3, name: 'Hermione'},
]

to:
{
1: { id: 1, name: "Harry" },
2: { id: 2, name: "Ron" },
3: { id: 3, name: "Hermione" },
}
*/

createEntityAdapterは、transformResponseと共に使用してデータを正規化し、ids配列を提供すること、sortComparerを使用して一貫してソートされたリストを維持すること、強力なTypeScriptサポートを維持することなど、createEntityAdapterが提供するその他の機能も利用できます。

こちらも参照してください 変換されたレスポンス形状を持つWebsocketチャットAPI。これは、ストリーミング更新を使用してさらにデータを更新しながら、createEntityAdapterと組み合わせてレスポンスデータを正規化するtransformResponseの例を示しています。

transformErrorResponseによるクエリレスポンスのカスタマイズ

createApiの個々のエンドポイントは、transformErrorResponseプロパティを受け入れます。これにより、クエリまたはミューテーションによって返されたエラーがキャッシュに保存される前に操作できます。

transformErrorResponseは、対応するエンドポイントに対して失敗したbaseQueryが返すエラーで呼び出され、transformErrorResponseの戻り値はそのエンドポイント呼び出しに関連付けられたキャッシュされたエラーとして使用されます。

デフォルトでは、サーバーからのペイロードが直接返されます。

function defaultTransformResponse(
baseQueryReturnValue: unknown,
meta: unknown,
arg: unknown
) {
return baseQueryReturnValue
}

変更するには、次のような関数を使用します。

深くネストされたエラーオブジェクトの展開
transformErrorResponse: (response, meta, arg) =>
response.data.some.deeply.nested.errorObject

transformErrorResponseは、変換されたレスポンスを決定する際に使用できる、baseQueryから返されたmetaプロパティを2番目の引数として呼び出されます。metaの値は、使用されているbaseQueryによって異なります。

transformErrorResponseのmetaの例
transformErrorResponse: (
response: { data: { sideA: Tracks; sideB: Tracks } },
meta,
arg,
) => {
if (meta?.coinFlip === 'heads') {
return response.data.sideA
}
return response.data.sideB
}

transformErrorResponseは、変換されたレスポンスを決定する際に使用できる、エンドポイントに提供されたargプロパティを3番目の引数として呼び出されます。argの値は、使用されているendpointと、クエリ/ミューテーション呼び出しで使用される引数によって異なります。

transformErrorResponseのargの例
transformErrorResponse: (response: Posts, meta, arg) => {
return {
originalArg: arg,
error: response,
}
}

queryFnによるクエリのカスタマイズ

RTK Queryには、HTTP URL(一般的なREST APIなど)と通信するエンドポイントを簡単に定義できるfetchBaseQueryが付属しています。GraphQLとの統合もあります。しかし、RTK Queryの中核は、HTTPリクエストだけでなく、*任意の*非同期リクエスト/レスポンスシーケンスの読み込み状態とキャッシュされた値を追跡することです。

RTK Queryは、任意の非同期ロジックを実行して結果を返すエンドポイントの定義をサポートしています。createApiの個々のエンドポイントは、queryFnプロパティを受け入れます。これにより、任意のロジックを内部に記述できる独自の非同期関数を記述できます。

これは、単一のエンドポイントに対して特に異なる動作が必要な場合、またはクエリ自体が無関係な場合に役立ちます。具体的には、以下のような場合です。

  • 異なるベースURLを使用する一度限りのクエリ
  • 自動再試行など、異なるリクエスト処理を使用する一度限りのクエリ
  • 異なるエラー処理動作を使用する一度限りのクエリ
  • FirebaseやSupabaseなどのサードパーティライブラリSDKを使用してリクエストを行うクエリ
  • 典型的なリクエスト/レスポンスではない非同期タスクを実行するクエリ
  • 単一のクエリで複数のリクエストを実行する(
  • 関連するクエリがない場合の無効化動作の活用(
  • 関連する初期リクエストがない場合のストリーミングアップデートの使用(

型シグネチャと利用可能なオプションについては、queryFn APIリファレンスも参照してください。

queryFnの実装

queryFnは、インラインのbaseQueryと考えることができます。これは、baseQueryと同じ引数、および提供されたbaseQuery関数自体(argapiextraOptions、およびbaseQuery)で呼び出されます。baseQueryと同様に、dataプロパティまたはerrorプロパティを持つオブジェクト、またはそのようなオブジェクトを返すpromiseを返すことが期待されます。

基本的なqueryFnの例

基本的なqueryFnの例
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
import { userAPI, User } from './userAPI'

const api = createApi({
baseQuery: fetchBaseQuery({ url: '/' }),
endpoints: (build) => ({
// normal HTTP endpoint using fetchBaseQuery
getPosts: build.query<PostsResponse, void>({
query: () => ({ url: 'posts' }),
}),
// endpoint with a custom `queryFn` and separate async logic
getUser: build.query<User, string>({
queryFn: async (userId: string) => {
try {
const user = await userApi.getUserById(userId)
// Return the result in an object with a `data` field
return { data: user }
} catch (error) {
// Catch any errors and return them as an object with an `error` field
return { error }
}
},
}),
}),
})

queryFn関数の引数

queryFnの引数の例
const queryFn = (
args,
{ signal, dispatch, getState },
extraOptions,
baseQuery,
) => {
// omitted
}

queryFn関数の戻り値

  1. 成功時の期待される結果形式
    return { data: YourData }
  2. エラー時の期待される結果形式
    return { error: YourError }
queryFnの戻り値の例
const queryFn = (
args,
{ signal, dispatch, getState },
extraOptions,
baseQuery,
) => {
if (Math.random() > 0.5) return { error: 'Too high!' }
return { data: 'All good!' }
}

例 - baseQuery

Axios baseQuery

この例では、非常に基本的なaxiosベースのbaseQueryユーティリティを実装しています。

基本的なaxios baseQuery
import { createApi } from '@reduxjs/toolkit/query'
import type { BaseQueryFn } from '@reduxjs/toolkit/query'
import axios from 'axios'
import type { AxiosRequestConfig, AxiosError } from 'axios'

const axiosBaseQuery =
(
{ baseUrl }: { baseUrl: string } = { baseUrl: '' }
): BaseQueryFn<
{
url: string
method?: AxiosRequestConfig['method']
data?: AxiosRequestConfig['data']
params?: AxiosRequestConfig['params']
headers?: AxiosRequestConfig['headers']
},
unknown,
unknown
> =>
async ({ url, method, data, params, headers }) => {
try {
const result = await axios({
url: baseUrl + url,
method,
data,
params,
headers,
})
return { data: result.data }
} catch (axiosError) {
const err = axiosError as AxiosError
return {
error: {
status: err.response?.status,
data: err.response?.data || err.message,
},
}
}
}

const api = createApi({
baseQuery: axiosBaseQuery({
baseUrl: 'https://example.com',
}),
endpoints(build) {
return {
query: build.query({ query: () => ({ url: '/query', method: 'get' }) }),
mutation: build.mutation({
query: () => ({ url: '/mutation', method: 'post' }),
}),
}
},
})

GraphQL baseQuery

この例では、非常に基本的なGraphQLベースのbaseQueryを実装しています。

基本的なGraphQL baseQuery
import { createApi } from '@reduxjs/toolkit/query'
import { request, gql, ClientError } from 'graphql-request'

const graphqlBaseQuery =
({ baseUrl }: { baseUrl: string }) =>
async ({ body }: { body: string }) => {
try {
const result = await request(baseUrl, body)
return { data: result }
} catch (error) {
if (error instanceof ClientError) {
return { error: { status: error.response.status, data: error } }
}
return { error: { status: 500, data: error } }
}
}

export const api = createApi({
baseQuery: graphqlBaseQuery({
baseUrl: 'https://graphqlzero.almansi.me/api',
}),
endpoints: (builder) => ({
getPosts: builder.query({
query: () => ({
body: gql`
query {
posts {
data {
id
title
}
}
}
`,
}),
transformResponse: (response) => response.posts.data,
}),
getPost: builder.query({
query: (id) => ({
body: gql`
query {
post(id: ${id}) {
id
title
body
}
}
`,
}),
transformResponse: (response) => response.post,
}),
}),
})

fetchBaseQueryを拡張した自動再認証

この例では、fetchBaseQueryをラップして、401 Unauthorizedエラーが発生した場合、認証トークンを更新しようとする追加のリクエストを送信し、再認証後に初期クエリを再試行します。

カスタムベースクエリを使用したaxiosのようなインターセプターのシミュレーション
import { fetchBaseQuery } from '@reduxjs/toolkit/query'
import type {
BaseQueryFn,
FetchArgs,
FetchBaseQueryError,
} from '@reduxjs/toolkit/query'
import { tokenReceived, loggedOut } from './authSlice'

const baseQuery = fetchBaseQuery({ baseUrl: '/' })
const baseQueryWithReauth: BaseQueryFn<
string | FetchArgs,
unknown,
FetchBaseQueryError
> = async (args, api, extraOptions) => {
let result = await baseQuery(args, api, extraOptions)
if (result.error && result.error.status === 401) {
// try to get a new token
const refreshResult = await baseQuery('/refreshToken', api, extraOptions)
if (refreshResult.data) {
// store the new token
api.dispatch(tokenReceived(refreshResult.data))
// retry the initial query
result = await baseQuery(args, api, extraOptions)
} else {
api.dispatch(loggedOut())
}
}
return result
}

複数の未承認エラーの防止

async-mutexを使用して、401 Unauthorizedエラーで複数回呼び出しが失敗した場合の'/refreshToken'への複数回の呼び出しを防ぎます。

'/refreshToken'への複数回の呼び出しの防止
import { fetchBaseQuery } from '@reduxjs/toolkit/query'
import type {
BaseQueryFn,
FetchArgs,
FetchBaseQueryError,
} from '@reduxjs/toolkit/query'
import { tokenReceived, loggedOut } from './authSlice'
import { Mutex } from 'async-mutex'

// create a new mutex
const mutex = new Mutex()
const baseQuery = fetchBaseQuery({ baseUrl: '/' })
const baseQueryWithReauth: BaseQueryFn<
string | FetchArgs,
unknown,
FetchBaseQueryError
> = async (args, api, extraOptions) => {
// wait until the mutex is available without locking it
await mutex.waitForUnlock()
let result = await baseQuery(args, api, extraOptions)
if (result.error && result.error.status === 401) {
// checking whether the mutex is locked
if (!mutex.isLocked()) {
const release = await mutex.acquire()
try {
const refreshResult = await baseQuery(
'/refreshToken',
api,
extraOptions
)
if (refreshResult.data) {
api.dispatch(tokenReceived(refreshResult.data))
// retry the initial query
result = await baseQuery(args, api, extraOptions)
} else {
api.dispatch(loggedOut())
}
} finally {
// release must be called once the mutex should be released again.
release()
}
} else {
// wait until the mutex is available without locking it
await mutex.waitForUnlock()
result = await baseQuery(args, api, extraOptions)
}
}
return result
}

自動再試行

RTK Queryは、API定義でbaseQueryをラップできるretryというユーティリティをエクスポートします。これは、基本的な指数バックオフを使用して、デフォルトで5回の試行を行います。

デフォルトの動作では、これらの間隔で再試行されます。

  1. 600ms * random(0.4, 1.4)
  2. 1200ms * random(0.4, 1.4)
  3. 2400ms * random(0.4, 1.4)
  4. 4800ms * random(0.4, 1.4)
  5. 9600ms * random(0.4, 1.4)
デフォルトでリクエストを5回再試行
import { createApi, fetchBaseQuery, retry } from '@reduxjs/toolkit/query/react'
interface Post {
id: number
name: string
}
type PostsResponse = Post[]

// maxRetries: 5 is the default, and can be omitted. Shown for documentation purposes.
const staggeredBaseQuery = retry(fetchBaseQuery({ baseUrl: '/' }), {
maxRetries: 5,
})
export const api = createApi({
baseQuery: staggeredBaseQuery,
endpoints: (build) => ({
getPosts: build.query<PostsResponse, void>({
query: () => ({ url: 'posts' }),
}),
getPost: build.query<PostsResponse, string>({
query: (id) => ({ url: `post/${id}` }),
extraOptions: { maxRetries: 8 }, // You can override the retry behavior on each endpoint
}),
}),
})

export const { useGetPostsQuery, useGetPostQuery } = api

特定のエンドポイントで再試行したくない場合は、maxRetries: 0を設定するだけです。

情報

フックが同時にdataerrorを返すことが可能です。デフォルトでは、RTK Queryは、更新またはガベージコレクションされるまで、最後の「良好な」結果をdataに保持します。

エラー再試行の中断

retryユーティリティには、再試行をすぐに中断するために使用できるfailメソッドプロパティが添付されています。これは、追加の再試行がすべて失敗することが保証され、冗長になることがわかっている状況で使用できます。

エラー再試行の中断
import { createApi, fetchBaseQuery, retry } from '@reduxjs/toolkit/query/react'
import type { FetchArgs } from '@reduxjs/toolkit/query'
interface Post {
id: number
name: string
}
type PostsResponse = Post[]

const staggeredBaseQueryWithBailOut = retry(
async (args: string | FetchArgs, api, extraOptions) => {
const result = await fetchBaseQuery({ baseUrl: '/api/' })(
args,
api,
extraOptions
)

// bail out of re-tries immediately if unauthorized,
// because we know successive re-retries would be redundant
if (result.error?.status === 401) {
retry.fail(result.error)
}

return result
},
{
maxRetries: 5,
}
)

export const api = createApi({
baseQuery: staggeredBaseQueryWithBailOut,
endpoints: (build) => ({
getPosts: build.query<PostsResponse, void>({
query: () => ({ url: 'posts' }),
}),
getPost: build.query<Post, string>({
query: (id) => ({ url: `post/${id}` }),
extraOptions: { maxRetries: 8 }, // You can override the retry behavior on each endpoint
}),
}),
})
export const { useGetPostsQuery, useGetPostQuery } = api

クエリへのメタ情報の追加

baseQueryは、戻り値にmetaプロパティを含めることもできます。これは、リクエストIDやタイムスタンプなど、リクエストに関連する追加情報を追加したい場合に役立ちます。

そのようなシナリオでは、戻り値は次のようになります。

  1. メタ情報を含む成功結果の期待される形式
    return { data: YourData, meta: YourMeta }
  2. メタ情報を含むエラー結果の期待される形式
    return { error: YourError, meta: YourMeta }
メタ情報を含むbaseQueryの例
import { fetchBaseQuery, createApi } from '@reduxjs/toolkit/query'
import type {
BaseQueryFn,
FetchArgs,
FetchBaseQueryError,
} from '@reduxjs/toolkit/query'
import type { FetchBaseQueryMeta } from '@reduxjs/toolkit/dist/query/fetchBaseQuery'
import { uuid } from './idGenerator'

type Meta = {
requestId: string
timestamp: number
}

const metaBaseQuery: BaseQueryFn<
string | FetchArgs,
unknown,
FetchBaseQueryError,
{},
Meta & FetchBaseQueryMeta
> = async (args, api, extraOptions) => {
const requestId = uuid()
const timestamp = Date.now()

const baseResult = await fetchBaseQuery({ baseUrl: '/' })(
args,
api,
extraOptions
)

return {
...baseResult,
meta: baseResult.meta && { ...baseResult.meta, requestId, timestamp },
}
}

const DAY_MS = 24 * 60 * 60 * 1000

interface Post {
id: number
name: string
timestamp: number
}
type PostsResponse = Post[]

const api = createApi({
baseQuery: metaBaseQuery,
endpoints: (build) => ({
// a theoretical endpoint where we only want to return data
// if request was performed past a certain date
getRecentPosts: build.query<PostsResponse, void>({
query: () => 'posts',
transformResponse: (returnValue: PostsResponse, meta) => {
// `meta` here contains our added `requestId` & `timestamp`, as well as
// `request` & `response` from fetchBaseQuery's meta object.
// These properties can be used to transform the response as desired.
if (!meta) return []
return returnValue.filter(
(post) => post.timestamp >= meta.timestamp - DAY_MS
)
},
}),
}),
})

Redux状態を使用した動的な基本URLの構築

場合によっては、Redux状態のプロパティから決定される動的に変更される基本URLが必要になる場合があります。baseQueryは、呼び出された時点の現在のストアの状態を提供するgetStateメソッドにアクセスできます。これは、部分的なURL文字列とストア状態の適切なデータを使用して、目的のURLを構築するために使用できます。

動的に生成された基本URLの例
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
import type {
BaseQueryFn,
FetchArgs,
FetchBaseQueryError,
} from '@reduxjs/toolkit/query/react'
import type { Post } from './types'
import { selectProjectId } from './projectSlice'
import type { RootState } from '../store'

const rawBaseQuery = fetchBaseQuery({
baseUrl: 'www.my-cool-site.com/',
})

const dynamicBaseQuery: BaseQueryFn<
string | FetchArgs,
unknown,
FetchBaseQueryError
> = async (args, api, extraOptions) => {
const projectId = selectProjectId(api.getState() as RootState)
// gracefully handle scenarios where data to generate the URL is missing
if (!projectId) {
return {
error: {
status: 400,
statusText: 'Bad Request',
data: 'No project ID received',
},
}
}

const urlEnd = typeof args === 'string' ? args : args.url
// construct a dynamically generated portion of the url
const adjustedUrl = `project/${projectId}/${urlEnd}`
const adjustedArgs =
typeof args === 'string' ? adjustedUrl : { ...args, url: adjustedUrl }
// provide the amended url and other params to the raw base query
return rawBaseQuery(adjustedArgs, api, extraOptions)
}

export const api = createApi({
baseQuery: dynamicBaseQuery,
endpoints: (builder) => ({
getPosts: builder.query<Post[], void>({
query: () => 'posts',
}),
}),
})

export const { useGetPostsQuery } = api

/*
Using `useGetPostsQuery()` where a `projectId` of 500 is in the redux state will result in
a request being sent to www.my-cool-site.com/project/500/posts
*/

例 - transformResponse

深くネストされたGraphQLデータの展開

GraphQL変換の例
import { createApi } from '@reduxjs/toolkit/query'
import { graphqlBaseQuery, gql } from './graphqlBaseQuery'

interface Post {
id: number
title: string
}

export const api = createApi({
baseQuery: graphqlBaseQuery({
baseUrl: '/graphql',
}),
endpoints: (builder) => ({
getPosts: builder.query<Post[], void>({
query: () => ({
body: gql`
query {
posts {
data {
id
title
}
}
}
`,
}),
transformResponse: (response: { posts: { data: Post[] } }) =>
response.posts.data,
}),
}),
})

createEntityAdapterを使用したデータの正規化

以下の例では、transformResponsecreateEntityAdapterと組み合わせて使用して、キャッシュに保存する前にデータを正規化します。

次のようなレスポンスの場合

[
{ id: 1, name: 'Harry' },
{ id: 2, name: 'Ron' },
{ id: 3, name: 'Hermione' },
]

正規化されたキャッシュデータは次のように保存されます。

{
ids: [1, 3, 2],
entities: {
1: { id: 1, name: "Harry" },
2: { id: 2, name: "Ron" },
3: { id: 3, name: "Hermione" },
}
}
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
import { createEntityAdapter } from '@reduxjs/toolkit'
import type { EntityState } from '@reduxjs/toolkit'

export interface Post {
id: number
name: string
}

const postsAdapter = createEntityAdapter<Post>({
sortComparer: (a, b) => a.name.localeCompare(b.name),
})

export const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: '/' }),
endpoints: (build) => ({
getPosts: build.query<EntityState<Post, number>, void>({
query: () => `posts`,
transformResponse(response: Post[]) {
return postsAdapter.addMany(postsAdapter.getInitialState(), response)
},
}),
}),
})

export const { useGetPostsQuery } = api

例 - queryFn

サードパーティSDKの使用

FirebaseやSupabaseなどの多くのサービスは、リクエストを行うための独自のSDKを提供しています。これらのSDKメソッドをqueryFnで使用できます。

基本的なサードパーティSDK
import { createApi, fakeBaseQuery } from '@reduxjs/toolkit/query/react'
import { supabase } from './supabaseApi'

export const supabaseApi = createApi({
reducerPath: 'supabaseApi',
baseQuery: fakeBaseQuery(),
endpoints: (builder) => ({
getBlogs: builder.query({
queryFn: async () => {
// Supabase conveniently already has `data` and `error` fields
const { data, error } = await supabase.from('blogs').select()
if (error) {
return { error }
}
return { data }
},
}),
}),
})

SDKを使用するカスタムベースクエリを作成し、メソッド名または引数をそのベースクエリに渡すエンドポイントを定義することもできます。

no-op queryFnの使用

特定のシナリオでは、リクエストの送信やデータの返却が状況に関係ないqueryまたはmutationが必要になる場合があります。このようなシナリオでは、invalidatesTagsプロパティを利用して、キャッシュに提供されている特定のtagsの再取得を強制します。

このようなシナリオで「エラーが発生したクエリの再取得」の詳細と例については、キャッシュへのエラーの提供も参照してください。

no-op queryFnの使用
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
import type { Post, User } from './types'

const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: '/' }),
tagTypes: ['Post', 'User'],
endpoints: (build) => ({
getPosts: build.query<Post[], void>({
query: () => 'posts',
providesTags: ['Post'],
}),

getUsers: build.query<User[], void>({
query: () => 'users',
providesTags: ['User'],
}),

refetchPostsAndUsers: build.mutation<null, void>({
// The query is not relevant here, so a `null` returning `queryFn` is used
queryFn: () => ({ data: null }),
// This mutation takes advantage of tag invalidation behaviour to trigger
// any queries that provide the 'Post' or 'User' tags to re-fetch if the queries
// are currently subscribed to the cached data
invalidatesTags: ['Post', 'User'],
}),
}),
})

初期リクエストのないストリーミングデータ

RTK Queryでは、エンドポイントがデータの初期リクエストを送信し、その後、更新が発生するとキャッシュされたデータにさらに更新を実行する定期的なストリーミングアップデートを行うことができます。ただし、初期リクエストはオプションであり、初期リクエストを送信せずにストリーミングアップデートを使用することもできます。

以下の例では、queryFnを使用して、初期リクエストを送信せずに空の配列でキャッシュデータを設定します。配列は後で、onCacheEntryAddedエンドポイントオプションを介してストリーミングアップデートを使用して設定され、受信されたデータがキャッシュデータに更新されます。

初期リクエストのないストリーミングデータ
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
import type { Message } from './types'

const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: '/' }),
tagTypes: ['Message'],
endpoints: (build) => ({
streamMessages: build.query<Message[], void>({
// The query is not relevant here as the data will be provided via streaming updates.
// A queryFn returning an empty array is used, with contents being populated via
// streaming updates below as they are received.
queryFn: () => ({ data: [] }),
async onCacheEntryAdded(arg, { updateCachedData, cacheEntryRemoved }) {
const ws = new WebSocket('ws://localhost:8080')
// populate the array with messages as they are received from the websocket
ws.addEventListener('message', (event) => {
updateCachedData((draft) => {
draft.push(JSON.parse(event.data))
})
})
await cacheEntryRemoved
ws.close()
},
}),
}),
})

単一のクエリで複数のリクエストを実行する

以下の例では、ランダムなユーザーのすべての投稿を取得するクエリを作成します。これは、ランダムなユーザーに対する最初のリクエストを行い、そのユーザーのすべての投稿を取得することによって行われます。queryFnを使用すると、2つのリクエストを単一のクエリに含めることができ、コンポーネントコード内でそのロジックをチェーンする必要がなくなります。

単一のクエリで複数のリクエストを実行する
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
import type { FetchBaseQueryError } from '@reduxjs/toolkit/query'
import type { Post, User } from './types'

const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: '/ ' }),
endpoints: (build) => ({
getRandomUserPosts: build.query<Post, void>({
async queryFn(_arg, _queryApi, _extraOptions, fetchWithBQ) {
// get a random user
const randomResult = await fetchWithBQ('users/random')
if (randomResult.error)
return { error: randomResult.error as FetchBaseQueryError }
const user = randomResult.data as User
const result = await fetchWithBQ(`user/${user.id}/posts`)
return result.data
? { data: result.data as Post }
: { error: result.error as FetchBaseQueryError }
},
}),
}),
})