Skip to content

add docs for RTKQ schema features #4926

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 11 commits into from
92 changes: 92 additions & 0 deletions docs/rtk-query/api/createApi.mdx
Original file line number Diff line number Diff line change
@@ -221,6 +221,20 @@ export type QueryDefinition<
updateCachedData, // available for query endpoints only
}: QueryCacheLifecycleApi,
): Promise<void>

argSchema?: StandardSchemaV1<QueryArg>

/* only available with `query`, not `queryFn` */
rawResponseSchema?: StandardSchemaV1<BaseQueryResult<BaseQuery>>

responseSchema?: StandardSchemaV1<ResultType>

/* only available with `query`, not `queryFn` */
rawErrorResponseSchema?: StandardSchemaV1<BaseQueryError<BaseQuery>>

errorResponseSchema?: StandardSchemaV1<BaseQueryError<BaseQuery>>

metaSchema?: StandardSchemaV1<BaseQueryMeta<BaseQuery>>
}
```

@@ -469,6 +483,26 @@ You can set this globally in `createApi`, but you can also override the default
If you specify `track: false` when manually dispatching queries, RTK Query will not be able to automatically refetch for you.
:::

### `onSchemaFailure`

[summary](docblock://query/createApi.ts?token=CreateApiOptions.onSchemaFailure)

[examples](docblock://query/createApi.ts?token=CreateApiOptions.onSchemaFailure)

:::note
You can set this globally in `createApi`, but you can also override the default value and have more granular control by passing `onSchemaFailure` to each individual endpoint definition.
:::

### `skipSchemaValidation`

[summary](docblock://query/createApi.ts?token=CreateApiOptions.skipSchemaValidation)

[examples](docblock://query/createApi.ts?token=CreateApiOptions.skipSchemaValidation)

:::note
You can set this globally in `createApi`, but you can also override the default value and have more granular control by passing `skipSchemaValidation` to each individual endpoint definition.
:::

## Endpoint Definition Parameters

### `query`
@@ -792,6 +826,64 @@ async function onCacheEntryAdded(
): Promise<void>
```

### Schema Validation

Endpoints can have schemas for runtime validation of various values. Any [Standard Schema](https://standardschema.dev/) compliant library can be used.

:::warning

Schema failures are treated as _fatal_, meaning that normal error handling such as tag invalidation will not be executed.

:::

#### `argSchema`

_(optional)_

[summary](docblock://query/endpointDefinitions.ts?token=CommonEndpointDefinition.argSchema)

[examples](docblock://query/endpointDefinitions.ts?token=CommonEndpointDefinition.argSchema)

#### `responseSchema`

_(optional)_

[summary](docblock://query/endpointDefinitions.ts?token=CommonEndpointDefinition.responseSchema)

[examples](docblock://query/endpointDefinitions.ts?token=CommonEndpointDefinition.responseSchema)

#### `rawResponseSchema`

_(optional, not applicable with `queryFn`)_

[summary](docblock://query/endpointDefinitions.ts?token=EndpointDefinitionWithQuery.rawResponseSchema)

[examples](docblock://query/endpointDefinitions.ts?token=EndpointDefinitionWithQuery.rawResponseSchema)

#### `errorResponseSchema`

_(optional)_

[summary](docblock://query/endpointDefinitions.ts?token=CommonEndpointDefinition.errorResponseSchema)

[examples](docblock://query/endpointDefinitions.ts?token=CommonEndpointDefinition.errorResponseSchema)

#### `rawErrorResponseSchema`

_(optional, not applicable with `queryFn`)_

[summary](docblock://query/endpointDefinitions.ts?token=EndpointDefinitionWithQuery.rawErrorResponseSchema)

[examples](docblock://query/endpointDefinitions.ts?token=EndpointDefinitionWithQuery.rawErrorResponseSchema)

#### `metaSchema`

_(optional)_

[summary](docblock://query/endpointDefinitions.ts?token=CommonEndpointDefinition.metaSchema)

[examples](docblock://query/endpointDefinitions.ts?token=CommonEndpointDefinition.metaSchema)

## Return value

See [the "created Api" API reference](./created-api/overview)
132 changes: 132 additions & 0 deletions docs/rtk-query/usage-with-typescript.mdx
Original file line number Diff line number Diff line change
@@ -703,3 +703,135 @@ function AddPost() {
)
}
```

## Schema Validation

Endpoints can have schemas for runtime validation of various values. Any [Standard Schema](https://standardschema.dev/) compliant library can be used. See [API reference](./api/createApi.mdx#schema-validation) for full list of available schemas.

When following the default approach of explicitly specifying type parameters for queries and mutations, the schemas will be required to match the types provided.

```ts title="Explicitly typed endpoint" no-transpile
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
import * as v from 'valibot'

const postSchema = v.object({
id: v.number(),
name: v.string(),
})
type Post = v.InferOutput<typeof postSchema>

const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: '/' }),
endpoints: (build) => ({
getPost: build.query<Post, { id: number }>({
query: ({ id }) => `/post/${id}`,
responseSchema: postSchema, // errors if type mismatch
}),
}),
})
```

Schemas can also be used as a source of inference, meaning that the type parameters can be omitted.

```ts title="Implicitly typed endpoint" no-transpile
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
import * as v from 'valibot'

const postSchema = v.object({
id: v.number(),
name: v.string(),
})
type Post = v.InferOutput<typeof postSchema>

const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: '/' }),
endpoints: (build) => ({
getPost: build.query({
// infer arg from here
query: ({ id }: { id: number }) => `/post/${id}`,
// infer result from here
responseSchema: postSchema,
}),
getTransformedPost: build.query({
// infer arg from here
query: ({ id }: { id: number }) => `/post/${id}`,
// infer untransformed result from here
rawResponseSchema: postSchema,
// infer transformed result from here
transformResponse: (response) => ({
...response,
published_at: new Date(response.published_at),
}),
}),
}),
})
```

:::warning

Schemas should _not_ perform any transformation that would change the type of the value.

```ts title="Incorrect usage" no-transpile
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
import * as v from 'valibot'
import { titleCase } from 'lodash'

const postSchema = v.object({
id: v.number(),
name: v.pipe(
v.string(),
v.transform(titleCase), // fine - string -> string
),
published_at: v.pipe(
v.string(),
// highlight-next-line
v.transform((s) => new Date(s)), // not allowed!
v.date(),
),
})
type Post = v.InferOutput<typeof postSchema>

const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: '/' }),
endpoints: (build) => ({
getPost: build.query<Post, { id: number }>({
query: ({ id }) => `/post/${id}`,
responseSchema: postSchema,
}),
}),
})
```

Instead, transformation should be done with `transformResponse` and `transformErrorResponse` (when using `query`) or inside `queryFn` (when using `queryFn`).

```ts title="Correct usage" no-transpile
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
import * as v from 'valibot'

const postSchema = v.object({
id: v.number(),
name: v.string(),
published_at: v.string(),
})
type RawPost = v.InferOutput<typeof postSchema>
type Post = Omit<RawPost, 'published_at'> & { published_at: Date }

const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: '/' }),
endpoints: (build) => ({
getPost: build.query<Post, { id: number }>({
query: ({ id }) => `/post/${id}`,
// use rawResponseSchema to validate *before* transformation
rawResponseSchema: postSchema,
// highlight-start
transformResponse: (response) => ({
...response,
published_at: new Date(response.published_at),
}),
// highlight-end
}),
}),
})
```

:::
56 changes: 56 additions & 0 deletions docs/rtk-query/usage/infinite-queries.mdx
Original file line number Diff line number Diff line change
@@ -531,3 +531,59 @@ const projectsApi = createApi({
}),
})
```

## Runtime Validation using Schemas

Endpoints can use any [Standard Schema](https://standardschema.dev/) compliant library for runtime validation of various values. See [API reference](../api/createApi.mdx#schema-validation) for full list of available schemas.

Most commonly, you'll want to use `responseSchema` to validate the response from the server (or `rawResponseSchema` when using `transformResponse`).

```ts title="Using responseSchema" no-transpile
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
import * as v from 'valibot'

const pokemonSchema = v.object({
id: v.number(),
name: v.string(),
})
type Pokemon = v.InferOutput<typeof pokemonSchema>
const transformedPokemonSchema = v.object({
...pokemonSchema.entries,
id: v.string(),
})
type TransformedPokemon = v.InferOutput<typeof transformedPokemonSchema>

const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: 'https://example.com/pokemon' }),
endpoints: (build) => ({
getInfinitePokemon: build.infiniteQuery<Pokemon[], string, number>({
query: ({ queryArg, pageParam }) => `type/${queryArg}?page=${pageParam}`,
// argSchema for infinite queries must have both queryArg and pageParam
argSchema: v.object({
queryArg: v.string(),
pageParam: v.number(),
}),
responseSchema: v.array(pokemonSchema),
}),
getTransformedPokemon: build.infiniteQuery<
TransformedPokemon[],
string,
number
>({
query: ({ queryArg, pageParam }) => `type/${queryArg}?page=${pageParam}`,
argSchema: v.object({
queryArg: v.string(),
pageParam: v.number(),
}),
rawResponseSchema: v.array(pokemonSchema),
transformResponse: (response) =>
response.map((pokemon) => ({
...pokemon,
id: String(pokemon.id),
})),
// responseSchema can still be provided, to validate the transformed response
responseSchema: v.array(transformedPokemonSchema),
}),
}),
})
```
57 changes: 57 additions & 0 deletions docs/rtk-query/usage/mutations.mdx
Original file line number Diff line number Diff line change
@@ -326,3 +326,60 @@ export const {
allow="geolocation; microphone; camera; midi; vr; accelerometer; gyroscope; payment; ambient-light-sensor; encrypted-media; usb"
sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin"
></iframe>

## Runtime Validation using Schemas

Endpoints can use any [Standard Schema](https://standardschema.dev/) compliant library for runtime validation of various values. See [API reference](../api/createApi.mdx#schema-validation) for full list of available schemas.

Most commonly, you'll want to use `responseSchema` to validate the response from the server (or `rawResponseSchema` when using `transformResponse`).

```ts title="Using responseSchema" no-transpile
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
import * as v from 'valibot'

const postSchema = v.object({
id: v.number(),
name: v.string(),
published_at: v.string(),
})
type Post = v.InferOutput<typeof postSchema>
const transformedPost = v.object({
...postSchema.entries,
published_at: v.date(),
})
type TransformedPost = v.InferOutput<typeof transformedPost>

const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: '/' }),
endpoints: (build) => ({
updatePost: build.mutation<Post, Partial<Post>>({
query(data) {
const { id, ...body } = data
return {
url: `post/${id}`,
method: 'PUT',
body,
}
},
responseSchema: postSchema,
}),
updatePostWithTransform: build.mutation<TransformedPost, Partial<Post>>({
query(data) {
const { id, ...body } = data
return {
url: `post/${id}`,
method: 'PUT',
body,
}
},
rawResponseSchema: postSchema,
transformResponse: (response) => ({
...response,
published_at: new Date(response.published_at),
}),
// responseSchema can still be provided, to validate the transformed response
responseSchema: transformedPost,
}),
}),
})
```
Loading
Oops, something went wrong.