A RetroSearch Logo

Home - News ( United States | United Kingdom | Italy | Germany ) - Football scores

Search Query:

Showing content from https://redux-toolkit.js.org/rtk-query/usage-with-typescript below:

Usage With TypeScript | Redux Toolkit

Usage With TypeScript

What You'll Learn

Introduction

As with the rest of the Redux Toolkit package, RTK Query is written in TypeScript, and its API is designed for seamless use in TypeScript applications.

This page provides details for using APIs included in RTK Query with TypeScript and how to type them correctly.

info

We strongly recommend using TypeScript 4.1+ with RTK Query for best results.

If you encounter any problems with the types that are not described on this page, please open an issue for discussion.

createApi Using auto-generated React Hooks

The React-specific entry point for RTK Query exports a version of createApi which automatically generates React hooks for each of the defined query & mutation endpoints.

To use the auto-generated React Hooks as a TypeScript user, you'll need to use TS4.1+.


import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
import type { Pokemon } from './types'


export const pokemonApi = createApi({
reducerPath: 'pokemonApi',
baseQuery: fetchBaseQuery({ baseUrl: 'https://pokeapi.co/api/v2/' }),
endpoints: (build) => ({
getPokemonByName: build.query<Pokemon, string>({
query: (name) => `pokemon/${name}`,
}),
}),
})



export const { useGetPokemonByNameQuery } = pokemonApi

import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'


export const pokemonApi = createApi({
reducerPath: 'pokemonApi',
baseQuery: fetchBaseQuery({ baseUrl: 'https://pokeapi.co/api/v2/' }),
endpoints: (build) => ({
getPokemonByName: build.query({
query: (name) => `pokemon/${name}`,
}),
}),
})



export const { useGetPokemonByNameQuery } = pokemonApi

For older versions of TS, you can use api.endpoints.[endpointName].useQuery/useMutation to access the same hooks.

Accessing api hooks directly

import { pokemonApi } from './pokemon'

const useGetPokemonByNameQuery = pokemonApi.endpoints.getPokemonByName.useQuery

Accessing api hooks directly

import { pokemonApi } from './pokemon'

const useGetPokemonByNameQuery = pokemonApi.endpoints.getPokemonByName.useQuery
Typing a baseQuery

Typing a custom baseQuery can be done using the BaseQueryFn type exported by RTK Query.

Base Query 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
dispatch: ThunkDispatch<any, any, any>
getState: () => unknown
}

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

The BaseQueryFn type accepts the following generics:

note

The meta property returned from a baseQuery will always be considered as potentially undefined, as a throw in the error case may result in it not being provided. When accessing values from the meta property, this should be accounted for, e.g. using optional chaining

Simple baseQuery TypeScript example

import { createApi } from '@reduxjs/toolkit/query'
import type { BaseQueryFn } from '@reduxjs/toolkit/query'

const simpleBaseQuery: BaseQueryFn<
string,
unknown,
{ reason: string },
{ shout?: boolean },
{ timestamp: number }
> = (arg, api, extraOptions) => {




const meta = { timestamp: Date.now() }

if (arg === 'forceFail') {
return {
error: {
reason: 'Intentionally requested to fail!',
meta,
},
}
}

if (extraOptions.shout) {
return { data: 'CONGRATULATIONS', meta }
}

return { data: 'congratulations', meta }
}

const api = createApi({
baseQuery: simpleBaseQuery,
endpoints: (build) => ({
getSupport: build.query({
query: () => 'support me',
extraOptions: {
shout: true,
},
}),
}),
})

Simple baseQuery TypeScript example

import { createApi } from '@reduxjs/toolkit/query'

const simpleBaseQuery = (arg, api, extraOptions) => {




const meta = { timestamp: Date.now() }

if (arg === 'forceFail') {
return {
error: {
reason: 'Intentionally requested to fail!',
meta,
},
}
}

if (extraOptions.shout) {
return { data: 'CONGRATULATIONS', meta }
}

return { data: 'congratulations', meta }
}

const api = createApi({
baseQuery: simpleBaseQuery,
endpoints: (build) => ({
getSupport: build.query({
query: () => 'support me',
extraOptions: {
shout: true,
},
}),
}),
})
Typing query and mutation endpoints

endpoints for an api are defined as an object using the builder syntax. Both query and mutation endpoints can be typed by providing types to the generics in <ResultType, QueryArg> format.

Defining endpoints with TypeScript

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

const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: '/' }),
endpoints: (build) => ({


getPost: build.query<Post, number>({


query: (id) => `post/${id}`,



transformResponse: (rawResult: { result: { post: Post } }, meta) => {




return rawResult.result.post
},
}),
}),
})

Defining endpoints with TypeScript

import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'

const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: '/' }),
endpoints: (build) => ({


getPost: build.query({


query: (id) => `post/${id}`,



transformResponse: (rawResult, meta) => {




return rawResult.result.post
},
}),
}),
})

note

queries and mutations can also have their return type defined by a baseQuery rather than the method shown above, however, unless you expect all of your queries and mutations to return the same type, it is recommended to leave the return type of the baseQuery as unknown.

Typing a queryFn

As mentioned in Typing query and mutation endpoints, a queryFn will receive its result & arg types from the generics provided to the corresponding built endpoint.

import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
import { getRandomName } from './randomData'

interface Post {
id: number
name: string
}

const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: '/' }),
endpoints: (build) => ({


getPost: build.query<Post, number>({


queryFn: (arg, queryApi, extraOptions, baseQuery) => {
const post: Post = {
id: arg,
name: getRandomName(),
}



return { data: post }
},
}),
}),
})
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
import { getRandomName } from './randomData'

const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: '/' }),
endpoints: (build) => ({


getPost: build.query({


queryFn: (arg, queryApi, extraOptions, baseQuery) => {
const post = {
id: arg,
name: getRandomName(),
}



return { data: post }
},
}),
}),
})

The error type that a queryFn must return is determined by the baseQuery provided to createApi.

With fetchBaseQuery, the error type is like so:

fetchBaseQuery error shape

{
status: number
data: any
}

An error case for the example above using queryFn and the error type from fetchBaseQuery could look like:

queryFn error example with error type from fetchBaseQuery

import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
import { getRandomName } from './randomData'

interface Post {
id: number
name: string
}

const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: '/' }),
endpoints: (build) => ({
getPost: build.query<Post, number>({
queryFn: (arg, queryApi, extraOptions, baseQuery) => {
if (arg <= 0) {
return {
error: {
status: 500,
statusText: 'Internal Server Error',
data: 'Invalid ID provided.',
},
}
}
const post: Post = {
id: arg,
name: getRandomName(),
}
return { data: post }
},
}),
}),
})

queryFn error example with error type from fetchBaseQuery

import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
import { getRandomName } from './randomData'

const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: '/' }),
endpoints: (build) => ({
getPost: build.query({
queryFn: (arg, queryApi, extraOptions, baseQuery) => {
if (arg <= 0) {
return {
error: {
status: 500,
statusText: 'Internal Server Error',
data: 'Invalid ID provided.',
},
}
}
const post = {
id: arg,
name: getRandomName(),
}
return { data: post }
},
}),
}),
})

For users who wish to only use queryFn for each endpoint and not include a baseQuery at all, RTK Query provides a fakeBaseQuery function that can be used to easily specify the error type each queryFn should return.

Excluding baseQuery for all endpoints

import { createApi, fakeBaseQuery } from '@reduxjs/toolkit/query'

type CustomErrorType = { reason: 'too cold' | 'too hot' }

const api = createApi({


baseQuery: fakeBaseQuery<CustomErrorType>(),
endpoints: (build) => ({
eatPorridge: build.query<'just right', 1 | 2 | 3>({
queryFn(seat) {
if (seat === 1) {
return { error: { reason: 'too cold' } }
}

if (seat === 2) {
return { error: { reason: 'too hot' } }
}

return { data: 'just right' }
},
}),
microwaveHotPocket: build.query<'delicious!', number>({
queryFn(duration) {
if (duration < 110) {
return { error: { reason: 'too cold' } }
}
if (duration > 140) {
return { error: { reason: 'too hot' } }
}

return { data: 'delicious!' }
},
}),
}),
})

Excluding baseQuery for all endpoints

import { createApi, fakeBaseQuery } from '@reduxjs/toolkit/query'

const api = createApi({


baseQuery: fakeBaseQuery(),
endpoints: (build) => ({
eatPorridge: build.query({
queryFn(seat) {
if (seat === 1) {
return { error: { reason: 'too cold' } }
}

if (seat === 2) {
return { error: { reason: 'too hot' } }
}

return { data: 'just right' }
},
}),
microwaveHotPocket: build.query({
queryFn(duration) {
if (duration < 110) {
return { error: { reason: 'too cold' } }
}
if (duration > 140) {
return { error: { reason: 'too hot' } }
}

return { data: 'delicious!' }
},
}),
}),
})
Typing dispatch and getState

createApi exposes the standard Redux dispatch and getState methods in several places, such as the lifecycleApi argument in lifecycle methods, or the baseQueryApi argument passed to queryFn methods and base query functions.

Normally, your application infers RootState and AppDispatch types from the store setup. Since createApi has to be called prior to creating the Redux store and is used as part of the store setup sequence, it can't directly know or use those types - it would cause a circular type inference error.

By default, dispatch usages inside of createApi will be typed as ThunkDispatch, and getState usages are typed as () => unknown. You will need to assert the type when needed - getState() as RootState. You may also include an explicit return type for the function as well, in order to break the circular type inference cycle:

const api = createApi({
baseQuery,
endpoints: (build) => ({
getTodos: build.query<Todo[], void>({
async queryFn() {

const state = getState() as RootState
const text = state.todoTexts[queryFnCalls]
return { data: [{ id: `${queryFnCalls++}`, text }] }
},
}),
}),
})
Typing providesTags/invalidatesTags

RTK Query utilizes a cache tag invalidation system in order to provide automated re-fetching of stale data.

When using the function notation, both the providesTags and invalidatesTags properties on endpoints are called with the following arguments:

A recommended use-case with providesTags when a query returns a list of items is to provide a tag for each item in the list using the entity ID, as well as a 'LIST' ID tag (see Advanced Invalidation with abstract tag IDs).

This is often written by spreading the result of mapping the received data into an array, as well as an additional item in the array for the 'LIST' ID tag. When spreading the mapped array, by default, TypeScript will broaden the type property to string. As the tag type must correspond to one of the string literals provided to the tagTypes property of the api, the broad string type will not satisfy TypeScript. In order to alleviate this, the tag type can be cast as const to prevent the type being broadened to string.

providesTags TypeScript example

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' as const, id })),
{ type: 'Posts', id: 'LIST' },
]
: [{ type: 'Posts', id: 'LIST' }],
}),
}),
})

providesTags TypeScript example

import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'

const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: '/' }),
tagTypes: ['Posts'],
endpoints: (build) => ({
getPosts: build.query({
query: () => 'posts',
providesTags: (result) =>
result
? [
...result.map(({ id }) => ({ type: 'Posts', id })),
{ type: 'Posts', id: 'LIST' },
]
: [{ type: 'Posts', id: 'LIST' }],
}),
}),
})
Skipping queries with TypeScript using skipToken

RTK Query provides the ability to conditionally skip queries from automatically running using the skip parameter as part of query hook options (see Conditional Fetching).

TypeScript users may find that they encounter invalid type scenarios when a query argument is typed to not be undefined, and they attempt to skip the query when an argument would not be valid.

API definition

import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
import type { Post } from './types'

export const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: '/' }),
endpoints: (build) => ({


getPost: build.query<Post, number>({
query: (id) => `post/${id}`,
}),
}),
})

export const { useGetPostQuery } = api

API definition

import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'

export const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: '/' }),
endpoints: (build) => ({


getPost: build.query({
query: (id) => `post/${id}`,
}),
}),
})

export const { useGetPostQuery } = api

Using skip in a component

import { useGetPostQuery } from './api'

function MaybePost({ id }: { id?: number }) {





const { data } = useGetPostQuery(id, { skip: !id })

return <div>...</div>
}

While you might be able to convince yourself that the query won't be called unless the id arg is a number at the time, TypeScript won't be convinced so easily.

RTK Query provides a skipToken export which can be used as an alternative to the skip option in order to skip queries, while remaining type-safe. When skipToken is passed as the query argument to useQuery, useQueryState or useQuerySubscription, it provides the same effect as setting skip: true in the query options, while also being a valid argument in scenarios where the arg might be undefined otherwise.

Using skipToken in a component

import { skipToken } from '@reduxjs/toolkit/query/react'
import { useGetPostQuery } from './api'

function MaybePost({ id }: { id?: number }) {


const { data } = useGetPostQuery(id ?? skipToken)

return <div>...</div>
}
Type safe error handling

When an error is gracefully provided from a base query, RTK query will provide the error directly. If an unexpected error is thrown by user code rather than a handled error, that error will be transformed into a SerializedError shape. Users should make sure that they are checking which kind of error they are dealing with before attempting to access its properties. This can be done in a type safe manner either by using a type guard, e.g. by checking for discriminated properties, or using a type predicate.

When using fetchBaseQuery, as your base query, errors will be of type FetchBaseQueryError | SerializedError. The specific shapes of those types can be seen below.

FetchBaseQueryError type

export type FetchBaseQueryError =
| {




status: number
data: unknown
}
| {




status: 'FETCH_ERROR'
data?: undefined
error: string
}
| {






status: 'PARSING_ERROR'
originalStatus: number
data: string
error: string
}
| {




status: 'CUSTOM_ERROR'
data?: unknown
error: string
}

SerializedError type

export interface SerializedError {
name?: string
message?: string
stack?: string
code?: string
}
Error result example

When using fetchBaseQuery, the error property returned from a hook will have the type FetchBaseQueryError | SerializedError | undefined. If an error is present, you can access error properties after narrowing the type to either FetchBaseQueryError or SerializedError.

import { usePostsQuery } from './services/api'

function PostDetail() {
const { data, error, isLoading } = usePostsQuery()

if (isLoading) {
return <div>Loading...</div>
}

if (error) {
if ('status' in error) {

const errMsg = 'error' in error ? error.error : JSON.stringify(error.data)

return (
<div>
<div>An error has occurred:</div>
<div>{errMsg}</div>
</div>
)
}

return <div>{error.message}</div>
}

if (data) {
return (
<div>
{data.map((post) => (
<div key={post.id}>Name: {post.name}</div>
))}
</div>
)
}

return null
}
Inline error handling example

When handling errors inline after unwrapping a mutation call, a thrown error will have a type of any for typescript versions below 4.4, or unknown for versions 4.4+. In order to safely access properties of the error, you must first narrow the type to a known type. This can be done using a type predicate as shown below.

services/helpers.ts

import { FetchBaseQueryError } from '@reduxjs/toolkit/query'




export function isFetchBaseQueryError(
error: unknown,
): error is FetchBaseQueryError {
return typeof error === 'object' && error != null && 'status' in error
}




export function isErrorWithMessage(
error: unknown,
): error is { message: string } {
return (
typeof error === 'object' &&
error != null &&
'message' in error &&
typeof (error as any).message === 'string'
)
}

addPost.tsx

import { useState } from 'react'
import { useSnackbar } from 'notistack'
import { api } from './services/api'
import { isFetchBaseQueryError, isErrorWithMessage } from './services/helpers'

function AddPost() {
const { enqueueSnackbar, closeSnackbar } = useSnackbar()
const [name, setName] = useState('')
const [addPost] = useAddPostMutation()

async function handleAddPost() {
try {
await addPost(name).unwrap()
setName('')
} catch (err) {
if (isFetchBaseQueryError(err)) {

const errMsg = 'error' in err ? err.error : JSON.stringify(err.data)
enqueueSnackbar(errMsg, { variant: 'error' })
} else if (isErrorWithMessage(err)) {

enqueueSnackbar(err.message, { variant: 'error' })
}
}
}

return (
<div>
<input value={name} onChange={(e) => setName(e.target.value)} />
<button>Add post</button>
</div>
)
}
Schema Validation

Endpoints can have schemas for runtime validation of query args, responses, and errors. Any Standard Schema compliant library can be used. See API reference 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.

Explicitly typed endpoint

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,
}),
}),
})

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

Implicitly typed endpoint

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({

query: ({ id }: { id: number }) => `/post/${id}`,

responseSchema: postSchema,
}),
getTransformedPost: build.query({

query: ({ id }: { id: number }) => `/post/${id}`,

rawResponseSchema: postSchema,

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.

Incorrect usage

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),
),
published_at: v.pipe(
v.string(),
v.transform((s) => new Date(s)),
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).

Correct usage

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}`,

rawResponseSchema: postSchema,
transformResponse: (response) => ({
...response,
published_at: new Date(response.published_at),
}),
}),
}),
})

RetroSearch is an open source project built by @garambo | Open a GitHub Issue

Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo

HTML: 3.2 | Encoding: UTF-8 | Version: 0.7.4