Skip to content

Commit e45b0f5

Browse files
committed
Improve queryFn exampls
1 parent 3718594 commit e45b0f5

File tree

1 file changed

+102
-18
lines changed

1 file changed

+102
-18
lines changed

docs/rtk-query/usage/customizing-queries.mdx

Lines changed: 102 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,30 @@ See also [`baseQuery API Reference`](../api/createApi.mdx#basequery).
2626

2727
RTK Query expects a `baseQuery` function to be called with three arguments: `args`, `api`, and `extraOptions`. It is expected to return an object with either a `data` or `error` property, or a promise that resolves to return such an object.
2828

29+
:::tip
30+
31+
Base query and query functions must _always_ catch errors themselves, and return it in an object!
32+
33+
```ts no-transpile
34+
function brokenCustomBaseQuery() {
35+
// ❌ Don't let this throw by itself
36+
const data = await fetchSomeData()
37+
return { data }
38+
}
39+
40+
function correctCustomBaseQuery() {
41+
// ✅ Catch errors and _return_ them so the RTKQ logic can track it
42+
try {
43+
const data = await fetchSomeData()
44+
return { data }
45+
} catch (error) {
46+
return { error }
47+
}
48+
}
49+
```
50+
51+
:::
52+
2953
#### baseQuery function arguments
3054

3155
```ts title="baseQuery example arguments" no-transpile
@@ -205,7 +229,11 @@ argument, which can be used while determining the transformed response. The valu
205229
dependent on the `baseQuery` used.
206230

207231
```ts title="transformErrorResponse meta example" no-transpile
208-
transformErrorResponse: (response: { data: { sideA: Tracks; sideB: Tracks } }, meta, arg) => {
232+
transformErrorResponse: (
233+
response: { data: { sideA: Tracks; sideB: Tracks } },
234+
meta,
235+
arg
236+
) => {
209237
if (meta?.coinFlip === 'heads') {
210238
return response.data.sideA
211239
}
@@ -228,13 +256,17 @@ transformErrorResponse: (response: Posts, meta, arg) => {
228256

229257
## Customizing queries with `queryFn`
230258

231-
Individual endpoints on [`createApi`](../api/createApi.mdx) accept a [`queryFn`](../api/createApi.mdx#queryfn) property which allows a given endpoint to ignore `baseQuery` for that endpoint by providing an inline function determining how that query resolves.
259+
RTK Query comes with `fetchBaseQuery` out of the box, which makes it straightforward to define endpoints that talk to HTTP URLs (such as a typical REST API). We also have integrations with GraphQL as well. However, at its core, RTK Query is really about tracking loading state and cached values for _any_ async request/response sequence, not just HTTP requests.
232260

233-
This can be useful for scenarios where you want to have particularly different behaviour for a single endpoint, or where the query itself is not relevant. Such situations may include:
261+
RTK Query supports defining endpoints that run arbitrary async logic and return a result. Individual endpoints on [`createApi`](../api/createApi.mdx) accept a [`queryFn`](../api/createApi.mdx#queryfn) property, which let you write your own async function with whatever logic you want inside.
262+
263+
This can be useful for scenarios where you want to have particularly different behaviour for a single endpoint, or where the query itself is not relevant, including:
234264

235265
- One-off queries that use a different base URL
236266
- One-off queries that use different request handling, such as automatic re-tries
237267
- One-off queries that use different error handling behaviour
268+
- Queries that make requests using a third-party library SDK, such as Firebase or Supabase
269+
- Queries that perform async tasks that are not a typical request/response
238270
- Performing multiple requests with a single query ([example](#performing-multiple-requests-with-a-single-query))
239271
- Leveraging invalidation behaviour with no relevant query ([example](#using-a-no-op-queryfn))
240272
- Using [Streaming Updates](./streaming-updates) with no relevant initial request ([example](#streaming-data-with-no-initial-request))
@@ -243,7 +275,39 @@ See also [`queryFn API Reference`](../api/createApi.mdx#queryfn) for the type si
243275

244276
### Implementing a `queryFn`
245277

246-
In order to use `queryFn`, it can be treated as an inline `baseQuery`. It will be called with the same arguments as `baseQuery`, as well as the provided `baseQuery` function itself (`arg`, `api`, `extraOptions`, and `baseQuery`). Similarly to `baseQuery`, it is expected to return an object with either a `data` or `error` property, or a promise that resolves to return such an object.
278+
A `queryFn` can be thought of as an inline `baseQuery`. It will be called with the same arguments as `baseQuery`, as well as the provided `baseQuery` function itself (`arg`, `api`, `extraOptions`, and `baseQuery`). Similarly to `baseQuery`, it is expected to return an object with either a `data` or `error` property, or a promise that resolves to return such an object.
279+
280+
#### Basic `queryFn` Example
281+
282+
```ts title="Basic queryFn example" no-transpile
283+
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
284+
import { userAPI, User } from './userAPI'
285+
286+
const api = createApi({
287+
baseQuery: fetchBaseQuery({ url: '/' }),
288+
endpoints: (build) => ({
289+
// normal HTTP endpoint using fetchBaseQuery
290+
getPosts: build.query<PostsResponse, void>({
291+
query: () => ({ url: 'posts' }),
292+
}),
293+
// highlight-start
294+
// endpoint with a custom `queryFn` and separate async logic
295+
getUser: build.query<User, string>({
296+
queryFn: async (userId: string) => {
297+
try {
298+
const user = await userApi.getUserById(userId)
299+
// Return the result in an object with a `data` field
300+
return { data: user }
301+
} catch (error) {
302+
// Catch any errors and return them as an object with an `error` field
303+
return { error }
304+
}
305+
},
306+
}),
307+
// highlight-end
308+
}),
309+
})
310+
```
247311

248312
#### queryFn function arguments
249313

@@ -318,7 +382,7 @@ const axiosBaseQuery =
318382
return {
319383
error: {
320384
status: err.response?.status,
321-
data: err.response?.data || err.message
385+
data: err.response?.data || err.message,
322386
},
323387
}
324388
}
@@ -610,10 +674,7 @@ In such a scenario, the return value would look like so:
610674
export declare const uuid: () => string
611675

612676
// file: metaBaseQuery.ts
613-
import {
614-
fetchBaseQuery,
615-
createApi,
616-
} from '@reduxjs/toolkit/query'
677+
import { fetchBaseQuery, createApi } from '@reduxjs/toolkit/query'
617678
import type {
618679
BaseQueryFn,
619680
FetchArgs,
@@ -710,10 +771,7 @@ export interface Post {
710771
}
711772
712773
// file: src/services/api.ts
713-
import {
714-
createApi,
715-
fetchBaseQuery,
716-
} from '@reduxjs/toolkit/query/react'
774+
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
717775
import type {
718776
BaseQueryFn,
719777
FetchArgs,
@@ -880,6 +938,34 @@ export const { useGetPostsQuery } = api
880938
881939
## Examples - `queryFn`
882940
941+
### Using a Third-Party SDK
942+
943+
Many services like Firebase and Supabase provide their own SDK to make requests. You can use those SDK methods in a `queryFn`:
944+
945+
```ts title="Basic Third-Party SDK" no-transpile
946+
import { createApi, fakeBaseQuery } from '@reduxjs/toolkit/query/react'
947+
import { supabase } from './supabaseApi'
948+
949+
export const supabaseApi = createApi({
950+
reducerPath: 'supabaseApi',
951+
baseQuery: fakeBaseQuery(),
952+
endpoints: (builder) => ({
953+
getBlogs: builder.query({
954+
queryFn: async () => {
955+
// Supabase conveniently already has `data` and `error` fields
956+
const { data, error } = await supabase.from('blogs').select()
957+
if (error) {
958+
return { error }
959+
}
960+
return { data }
961+
},
962+
}),
963+
}),
964+
})
965+
```
966+
967+
You could also try creating a custom base query that uses the SDK, and define endpoints that pass method names or args into that base query.
968+
883969
### Using a no-op queryFn
884970

885971
In certain scenarios, you may wish to have a `query` or `mutation` where sending a request or returning data is not relevant for the situation. Such a scenario would be to leverage the `invalidatesTags` property to force re-fetch specific `tags` that have been provided to the cache.
@@ -987,10 +1073,7 @@ export interface User {
9871073
}
9881074

9891075
// file: api.ts
990-
import {
991-
createApi,
992-
fetchBaseQuery,
993-
} from '@reduxjs/toolkit/query'
1076+
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
9941077
import type { FetchBaseQueryError } from '@reduxjs/toolkit/query'
9951078
import type { Post, User } from './types'
9961079

@@ -1001,7 +1084,8 @@ const api = createApi({
10011084
async queryFn(_arg, _queryApi, _extraOptions, fetchWithBQ) {
10021085
// get a random user
10031086
const randomResult = await fetchWithBQ('users/random')
1004-
if (randomResult.error) return { error: randomResult.error as FetchBaseQueryError }
1087+
if (randomResult.error)
1088+
return { error: randomResult.error as FetchBaseQueryError }
10051089
const user = randomResult.data as User
10061090
const result = await fetchWithBQ(`user/${user.id}/posts`)
10071091
return result.data

0 commit comments

Comments
 (0)