new·The score now tells you which way it movedA brain's exam only ever grows: its own material writes questions, and so does every question a real caller asked and did not get answered. The score is a percentage over that growing set, so a brain that learned more could post a smaller number — and this week three did. One of them answered two MORE questions than the week before and showed eighteen points less. Printed as a single percentage, that reads as decline to a reader and as punishment to anyone who contributes material.all news →
mozg.beta
Sign in

Redux Toolkit · API · all subjects

createasyncthunk

55 notes, read out of this brain and free to use. Each one was extracted from a source and is re-checked against its exam.

createAsyncThunk overview and purpose

createAsyncThunk is a function that accepts a Redux action type string and a callback function that should return a promise. It generates promise lifecycle action types based on the action type prefix provided and returns a thunk action creator that will run the promise callback and dispatch the lifecycle actions based on the returned promise. It abstracts the standard recommended approach for handling async request lifecycles. It does not generate any reducer functions, since it does not know what data is being fetched, how to track loading state, or how the returned data needs to be processed.

createAsyncThunk parameters overview

createAsyncThunk accepts three parameters: a string action type value, a payloadCreator callback, and an options object.

createAsyncThunk type parameter

The type parameter is a string that will be used to generate additional Redux action type constants representing the lifecycle of an async request. For example, a type argument of 'users/requestStatus' will generate these action types: pending as 'users/requestStatus/pending', fulfilled as 'users/requestStatus/fulfilled', and rejected as 'users/requestStatus/rejected'.

createAsyncThunk payloadCreator callback

The payloadCreator is a callback function that should return a promise containing the result of some asynchronous logic. It may also return a value synchronously. If there is an error, it should either return a rejected promise containing an Error instance or a plain value such as a descriptive error message, or a resolved promise with a RejectWithValue argument as returned by thunkAPI.rejectWithValue function. The payloadCreator function will be called with two arguments: arg (a single value containing the first parameter passed to the thunk action creator when dispatched) and thunkAPI (an object containing all parameters normally passed to a Redux thunk function, plus additional options).

createAsyncThunk thunkAPI object properties

The thunkAPI object passed to payloadCreator contains: dispatch (the Redux store dispatch method), getState (the Redux store getState method), extra (the extra argument given to the thunk middleware on setup, if available), requestId (a unique string ID value automatically generated to identify the request sequence), signal (an AbortController.signal object that may be used to see if another part of the app logic has marked this request as needing cancellation), rejectWithValue(value, [meta]) (a utility function that can be returned or thrown to return a rejected response with a defined payload and meta), and fulfillWithValue(value, meta) (a utility function that can be returned to fulfill with a value while having the ability to add to fulfilledAction.meta).

createAsyncThunk options parameters

createAsyncThunk accepts the following optional fields in the options object: condition(arg, { getState, extra }): boolean | Promise<boolean> - a callback that can be used to skip execution of the payload creator and all action dispatches; dispatchConditionRejection - if condition() returns false, the default behavior is that no actions will be dispatched at all, but if set to true a rejected action will be dispatched when the thunk was canceled; idGenerator(arg): string - a function to use when generating the requestId for the request sequence, defaults to nanoid; serializeError(error: unknown) => any - replaces the internal miniSerializeError method with custom serialization logic; getPendingMeta({ arg, requestId }, { getState, extra }): any - a function to create an object that will be merged into the pendingAction.meta field.

createAsyncThunk return value structure

createAsyncThunk returns a standard Redux thunk action creator with plain action creators for the pending, fulfilled, and rejected cases attached as nested fields. The returned thunk action creator generates four functions: the main thunk action creator that kicks off the async payload callback, plus fetchUserById.pending (dispatches a pending action), fetchUserById.fulfilled (dispatches a fulfilled action), and fetchUserById.rejected (dispatches a rejected action).

createAsyncThunk thunk dispatch behavior

When dispatched, the thunk will: dispatch the pending action, call the payloadCreator callback and wait for the returned promise to settle, and when the promise settles: if the promise resolved successfully, dispatch the fulfilled action with the promise value as action.payload; if the promise resolved with a rejectWithValue(value) return value, dispatch the rejected action with the value passed into action.payload and 'Rejected' as action.error.message; if the promise failed and was not handled with rejectWithValue, dispatch the rejected action with a serialized version of the error value as action.error; return a fulfilled promise containing the final dispatched action (either the fulfilled or rejected action object).

createAsyncThunk thunk dispatch options

The returned thunk action creator accepts an optional second argument with the following options: signal - an optional AbortSignal that will be tracked by the internal abort signal.

createAsyncThunk PendingAction interface

PendingAction has the following structure: type (string), payload (undefined), and meta containing requestId (string) and arg (ThunkArg).

createAsyncThunk FulfilledAction interface

FulfilledAction has the following structure: type (string), payload (PromiseResult), and meta containing requestId (string) and arg (ThunkArg).

createAsyncThunk RejectedWithValueAction interface

RejectedWithValueAction has the following structure: type (string), payload (RejectedValue), error ({ message: 'Rejected' }), and meta containing requestId (string), arg (ThunkArg), and aborted (boolean).

createAsyncThunk SerializedError interface

SerializedError has the following optional properties: name (string), message (string), stack (string), and code (string).

createAsyncThunk action creator signatures

The action creators have these signatures: Pending = <ThunkArg>(requestId: string, arg: ThunkArg) => PendingAction<ThunkArg>; Fulfilled = <ThunkArg, PromiseResult>(payload: PromiseResult, requestId: string, arg: ThunkArg) => FulfilledAction<ThunkArg, PromiseResult>; Rejected = <ThunkArg>(requestId: string, arg: ThunkArg) => RejectedAction<ThunkArg>; RejectedWithValue = <ThunkArg, RejectedValue>(requestId: string, arg: ThunkArg) => RejectedWithValueAction<ThunkArg, RejectedValue>.

createAsyncThunk settled matcher

A settled matcher is attached to the thunk for matching against both fulfilled and rejected actions. This is conceptually similar to a finally block. The settled matcher should be used with addMatcher instead of addCase, since settled is a matcher rather than an action creator.

createAsyncThunk unwrap method on returned promise

The promise returned by the dispatched thunk has an unwrap property which can be called to extract the payload of a fulfilled action or to throw either the error or, if available, payload created by rejectWithValue from a rejected action.

createAsyncThunk always returns resolved promise

Thunks generated by createAsyncThunk will always return a resolved promise with either the fulfilled action object or rejected action object inside, as appropriate. A failed request or error in a thunk will never return a rejected promise.

createAsyncThunk unwrapResult exported function

Redux Toolkit exports an unwrapResult function that can be used to extract the payload or throw an error from an action, similar to the .unwrap() method on the promise. It takes the result action and returns the payload or throws an error.

createAsyncThunk error handling with SerializedError

When a payloadCreator returns a rejected promise, the thunk will dispatch a rejected action containing an automatically-serialized version of the error as action.error. To ensure serializability, everything that does not match the SerializedError interface will be removed from it. SerializedError contains optional properties: name, message, stack, and code.

createAsyncThunk rejectWithValue for custom errors

If you need to customize the contents of the rejected action, you should catch errors yourself and return a new value using the thunkAPI.rejectWithValue utility. Doing return rejectWithValue(errorPayload) will cause the rejected action to use that value as action.payload. This approach should also be used if an API response succeeds but contains additional error details that the reducer should know about, such as field-level validation errors.

createAsyncThunk condition callback for canceling before execution

If you need to cancel a thunk before the payload creator is called, you may provide a condition callback as an option after the payload creator. The callback will receive the thunk argument and an object with {getState, extra} as parameters and use those to decide whether to continue or not. If the execution should be canceled, the condition callback should return a literal false value or a promise that resolves to false. If a promise is returned, the thunk waits for it to get fulfilled before dispatching the pending action, otherwise it proceeds synchronously.

createAsyncThunk dispatchConditionRejection option

If condition() returns false, the default behavior is that no actions will be dispatched at all. If you still want a rejected action to be dispatched when the thunk was canceled, pass in {condition, dispatchConditionRejection: true}.

createAsyncThunk abort method for canceling while running

If you want to cancel a running thunk before it has finished, you can use the abort method of the promise returned by dispatch(fetchUserById(userId)). After a thunk has been cancelled this way, it will dispatch and return a 'thunkName/rejected' action with an AbortError on the error property. The thunk will not dispatch any further actions.

createAsyncThunk signal.aborted property for checking cancellation

You can use the signal.aborted property to regularly check if the thunk has been aborted and in that case stop costly long-running work. This is passed via thunkAPI.signal in the payloadCreator.

createAsyncThunk signal abort event listener

You can call signal.addEventListener('abort', callback) to have logic inside the thunk be notified when promise.abort() was called. This can be used in conjunction with cancellation mechanisms like axios CancelToken.

createAsyncThunk meta object for checking cancellation status

To investigate behavior around thunk cancellation, you can inspect various properties on the meta object of the dispatched action. If a thunk was cancelled before execution, meta.condition will be true. If it was aborted while running, meta.aborted will be true. If neither of those is true, the thunk was not cancelled, it was simply rejected. If the thunk was not rejected, both meta.aborted and meta.condition will be undefined.

createAsyncThunk basic usage example

```ts import { createAsyncThunk, createSlice } from '@reduxjs/toolkit' import { userAPI } from './userAPI' // First, create the thunk const fetchUserById = createAsyncThunk( 'users/fetchByIdStatus', async (userId: number, thunkAPI) => { const response = await userAPI.fetchById(userId) return response.data }, ) interface UsersState { entities: User[] loading: 'idle' | 'pending' | 'succeeded' | 'failed' } const initialState = { entities: [], loading: 'idle', } satisfies UserState as UsersState // Then, handle actions in your reducers: const usersSlice = createSlice({ name: 'users', initialState, reducers: { // standard reducer logic, with auto-generated action types per reducer }, extraReducers: (builder) => { // Add reducers for additional action types here, and handle loading state as needed builder.addCase(fetchUserById.fulfilled, (state, action) => { // Add user to the state array state.entities.push(action.payload) }) }, }) // Later, dispatch the thunk as needed in the app dispatch(fetchUserById(123)) ```

createAsyncThunk unwrap usage example

```ts // in the component const onClick = () => { dispatch(fetchUserById(userId)) .unwrap() .then((originalPromiseResult) => { // handle result here }) .catch((rejectedValueOrSerializedError) => { // handle error here }) } ```

createAsyncThunk unwrap with async await example

```ts // in the component const onClick = async () => { try { const originalPromiseResult = await dispatch(fetchUserById(userId)).unwrap() // handle result here } catch (rejectedValueOrSerializedError) { // handle error here } } ```

createAsyncThunk unwrapResult with async await example

```ts import { unwrapResult } from '@reduxjs/toolkit' // in the component const onClick = async () => { try { const resultAction = await dispatch(fetchUserById(userId)) const originalPromiseResult = unwrapResult(resultAction) // handle result here } catch (rejectedValueOrSerializedError) { // handle error here } } ```

createAsyncThunk rejectWithValue error handling example

```ts const updateUser = createAsyncThunk( 'users/update', async (userData, { rejectWithValue }) => { const { id, ...fields } = userData try { const response = await userAPI.updateById(id, fields) return response.data.user } catch (err) { // Use `err.response.data` as `action.payload` for a `rejected` action, // by explicitly returning it using the `rejectWithValue()` utility return rejectWithValue(err.response.data) } }, ) ```

createAsyncThunk condition callback example

```ts const fetchUserById = createAsyncThunk( 'users/fetchByIdStatus', async (userId: number, thunkAPI) => { const response = await userAPI.fetchById(userId) return response.data }, { condition: (userId, { getState, extra }) => { const { users } = getState() const fetchStatus = users.requests[userId] if (fetchStatus === 'fulfilled' || fetchStatus === 'loading') { // Already fetched or in progress, don't need to re-fetch return false } }, }, ) ```

createAsyncThunk abort dispatch option example

```ts const externalController = new AbortController() dispatch(fetchUserById(123, { signal: externalController.signal })) externalController.abort() ```

createAsyncThunk abort method usage in useEffect example

```ts function MyComponent(props: { userId: string }) { const dispatch = useAppDispatch() React.useEffect(() => { // Dispatching the thunk returns a promise const promise = dispatch(fetchUserById(props.userId)) return () => { // `createAsyncThunk` attaches an `abort()` method to the promise promise.abort() } }, [props.userId]) } ```

createAsyncThunk fetch with AbortSignal example

```ts import { createAsyncThunk } from '@reduxjs/toolkit' const fetchUserById = createAsyncThunk( 'users/fetchById', async (userId: string, thunkAPI) => { const response = await fetch(`https://reqres.in/api/users/${userId}`, { signal: thunkAPI.signal, }) return await response.json() }, ) ```

createAsyncThunk signal.aborted checking example

```ts import { createAsyncThunk } from '@reduxjs/toolkit' const readStream = createAsyncThunk( 'readStream', async (stream: ReadableStream, { signal }) => { const reader = stream.getReader() let done = false let result = '' while (!done) { if (signal.aborted) { throw new Error('stop the work, this has been aborted!') } const read = await reader.read() result += read.value done = read.done } return result }, ) ```

createAsyncThunk signal abort event with axios example

```ts import { createAsyncThunk } from '@reduxjs/toolkit' import axios from 'axios' const fetchUserById = createAsyncThunk( 'users/fetchById', async (userId: string, { signal }) => { const source = axios.CancelToken.source() signal.addEventListener('abort', () => { source.cancel() }) const response = await axios.get(`https://reqres.in/api/users/${userId}`, { cancelToken: source.token, }) return response.data }, ) ```

createAsyncThunk condition cancellation test example

```ts import { createAsyncThunk } from '@reduxjs/toolkit' test('this thunk should always be skipped', async () => { const thunk = createAsyncThunk( 'users/fetchById', async () => throw new Error('This promise should never be entered'), { condition: () => false, } ) const result = await thunk()(dispatch, getState, null) expect(result.meta.condition).toBe(true) expect(result.meta.aborted).toBe(false) }) ```

createAsyncThunk with loading state example

```ts import { createAsyncThunk, createSlice } from '@reduxjs/toolkit' import { userAPI, User } from './userAPI' const fetchUserById = createAsyncThunk< User, string, { state: { users: { loading: string; currentRequestId: string } } } >('users/fetchByIdStatus', async (userId: string, { getState, requestId }) => { const { currentRequestId, loading } = getState().users if (loading !== 'pending' || requestId !== currentRequestId) { return } const response = await userAPI.fetchById(userId) return response.data }) const usersSlice = createSlice({ name: 'users', initialState: { entities: [], loading: 'idle', currentRequestId: undefined, error: null, }, reducers: {}, extraReducers: (builder) => { builder .addCase(fetchUserById.pending, (state, action) => { if (state.loading === 'idle') { state.loading = 'pending' state.currentRequestId = action.meta.requestId } }) .addCase(fetchUserById.fulfilled, (state, action) => { const { requestId } = action.meta if ( state.loading === 'pending' && state.currentRequestId === requestId ) { state.loading = 'idle' state.entities.push(action.payload) state.currentRequestId = undefined } }) .addCase(fetchUserById.rejected, (state, action) => { const { requestId } = action.meta if ( state.loading === 'pending' && state.currentRequestId === requestId ) { state.loading = 'idle' state.error = action.error state.currentRequestId = undefined } }) }, }) const UsersComponent = () => { const { entities, loading, error } = useSelector((state) => state.users) const dispatch = useDispatch() const fetchOneUser = async (userId) => { try { const user = await dispatch(fetchUserById(userId)).unwrap() showToast('success', `Fetched ${user.name}`) } catch (err) { showToast('error', `Fetch failed: ${err.message}`) } } // render UI here } ```

createAsyncThunk with rejectWithValue and validation errors example

```ts import { createAsyncThunk, createSlice } from '@reduxjs/toolkit' import { userAPI } from './userAPI' import type { AxiosError } from 'axios' export interface User { id: string first_name: string last_name: string email: string } interface ValidationErrors { errorMessage: string field_errors: Record<string, string> } interface UpdateUserResponse { user: User success: boolean } export const updateUser = createAsyncThunk< User, { id: string } & Partial<User>, { rejectValue: ValidationErrors } >('users/update', async (userData, { rejectWithValue }) => { try { const { id, ...fields } = userData const response = await userAPI.updateById<UpdateUserResponse>(id, fields) return response.data.user } catch (err) { let error: AxiosError<ValidationErrors> = err if (!error.response) { throw err } return rejectWithValue(error.response.data) } }) interface UsersState { error: string | null | undefined entities: Record<string, User> } const initialState = { entities: {}, error: null, } satisfies UsersState as UsersState const usersSlice = createSlice({ name: 'users', initialState, reducers: {}, extraReducers: (builder) => { builder.addCase(updateUser.fulfilled, (state, { payload }) => { state.entities[payload.id] = payload }) builder.addCase(updateUser.rejected, (state, action) => { if (action.payload) { state.error = action.payload.errorMessage } else { state.error = action.error.message } }) }, }) ```

createAsyncThunk in component with validation errors example

```ts import React from 'react' import { useAppDispatch } from '../store' import type { RootState } from '../store' import { useSelector } from 'react-redux' import { updateUser } from './slice' import type { User } from './slice' import type { FormikHelpers } from 'formik' import { showToast } from 'some-toast-library' interface FormValues extends Omit<User, 'id'> {} const UsersComponent = (props: { id: string }) => { const { entities, error } = useSelector((state: RootState) => state.users) const dispatch = useAppDispatch() const handleUpdateUser = async ( values: FormValues, formikHelpers: FormikHelpers<FormValues>, ) => { const resultAction = await dispatch(updateUser({ id: props.id, ...values })) if (updateUser.fulfilled.match(resultAction)) { const user = resultAction.payload showToast('success', `Updated ${user.first_name} ${user.last_name}`) } else { if (resultAction.payload) { formikHelpers.setErrors(resultAction.payload.field_errors) } else { showToast('error', `Update failed: ${resultAction.error}`) } } } // render UI here } ```

createAsyncThunk automatically generates pending, fulfilled, and rejected actions

createAsyncThunk accepts a type prefix string and an async function. It automatically generates three action types and creators: a pending action dispatched before the async function runs, a fulfilled action if the promise resolves with the returned value as payload, and a rejected action if the promise rejects with the error as payload.

createAsyncThunk basic usage

Example of creating an async thunk: `const fetchUserById = createAsyncThunk('users/fetchByIdStatus', async (userId, thunkAPI) => { const response = await userAPI.fetchById(userId); return response.data })`. The first argument is the action type prefix, the second is an async payload creator callback. The thunk is then dispatched like `dispatch(fetchUserById(123))`.

createAsyncThunk payload creator receives thunkAPI argument

The payload creator callback in createAsyncThunk receives two arguments: the first is the value passed to the thunk when dispatched, and the second is a thunkAPI object containing dispatch, getState, extra, requestId, and signal properties.

ThunkAPI object interface

The thunkAPI object passed to createAsyncThunk payload creator has the interface: { dispatch: Function, getState: Function, extra?: any, requestId: string, signal: AbortSignal }. The signal is an AbortController signal that can be used to detect cancellation.

Thunks defined in slice files have access to slice actions

When writing thunk functions in the same file as a slice created with createSlice, the thunks can import and use the plain action creators from that slice, making it easy to dispatch state changes as part of async logic.

Basic createAsyncThunk typing

In most cases, no explicit types need to be declared for createAsyncThunk. Provide a type for the payloadCreator's first argument, and the resulting thunk will accept the same type. The return type is reflected in all generated action types.

createAsyncThunk parameter and return type inference

Declare the payloadCreator argument type to automatically infer the thunk parameter type. The return type from payloadCreator is reflected in all generated action types like fulfilled and rejected.

Handle createAsyncThunk responses with unwrap method

The preferred approach to handling thunk responses is via the unwrap method. Use: const result = await dispatch(updateUser(userData)).unwrap() in a try-catch block to handle success and error cases.

AsyncThunkConfig type definition

Pass an object as the third generic argument to createAsyncThunk to define types for thunkApi fields. The AsyncThunkConfig object can include: state, dispatch, extra, rejectValue, serializedErrorType, pendingMeta, fulfilledMeta, rejectedMeta.

Manually define thunkApi types in createAsyncThunk

To use thunkApi fields (dispatch, getState, extra, rejectWithValue), define generic arguments. Provide an AsyncThunkConfig object as the third generic with types for the fields you need, then both Returned and ThunkArg must also be defined explicitly.

createAsyncThunk with rejectValue typing

Pass a rejectValue type in the AsyncThunkConfig third generic argument. This allows you to use thunkApi.rejectWithValue(knownPayload) and reference the typed error payload in reducers.

createAsyncThunk extra argument typing

To type the extra argument available as thunkApi.extra, provide the type in the AsyncThunkConfig object: { extra: { jwt: string } }

createAsyncThunk.withTypes pre-typed version

As of RTK 1.9, call createAsyncThunk.withTypes<AsyncThunkConfig>() to create a pre-typed version with built-in state, dispatch, and extra types. This eliminates the need to repeat types in every createAsyncThunk call.

Use action.payload and match for type guards

Leverage checks against action.payload and the match method from createAction as type guards when accessing known properties. Example: if (updateUser.fulfilled.match(resultAction)) { const user = resultAction.payload }

Give your agent this brain