Parameters
createAsyncThunk(Type, PayloadCreator, [Options]);
1. Type
A string that will be prepended with the three states and dispatched as they happen.
2. PayloadCreator
async (params, thunkApi) => {}
params: What you’ll call the thunk with. This could be the request body, an id, queryString variables etc
thunkApi: An object containing
dispatch: To dispatch actions other than the three that are generated
getState: Gets the current root store state
extra: The argument passed to configureStore({middleware: getDefaultMiddleware => getDefaultMiddleware({thunk: {extraArgument: "**this**"}})})
requestId: unique generated ID
signal: AbortController.signal when you want to implement cancellation (see Cancellation)
rejectWithValue(value; [meta]) and fulfillWithValue(value, [meta]): or just throw/return!
3. Options
An option object that can contain:
condition: if this function returns false, no actions will be dispatched, see Canceling before execution
dispatchConditionRejection: if condition returned false and this field is true, the rejected action will still be dispatched
idGenerator: Uses nanoid by default
serializeError: Serialize errors in a different way than sindresorhus/serialize-error
getPendingMeta: merge extra data into pendingAction.meta
Unwrapping
createAsyncThunk always returns a resolved promise, even if it was rejected. To get the actual contents, .unwrap() it!
dispatch(fetchStuff({id: 42}))
.unwrap()
.then(success => {})
.catch(error => {})
Matchers
We already saw the fetchStuff.settled (for handling fulfilled or rejected) matcher above but there are other ways to handle multiple actions with a single matcher.
import { isPending } from "@reduxjs/toolkit";
type GenericAsyncThunk = AsyncThunk<unknown, unknown, any>
type PendingAction = ReturnType<GenericAsyncThunk['pending']>
type RejectedAction = ReturnType<GenericAsyncThunk['rejected']>
type FulfilledAction = ReturnType<GenericAsyncThunk['fulfilled']>
const mySlice = createSlice({
extraReducers: builder => {
builder.addMatcher<RejectedAction>(
action => action.type.endsWith('/rejected'),
(state, action) => {
state.status = 'failed';
}
);
builder.addMatcher(isPending, (state, action) => {
// Or by using the existing isPending matcher utility
// Other utilities: isAllOf, isAnyOf
// isAsyncThunkAction, isFulfilled, isRejected(WithValue)
});
builder.addDefaultCase((state, action) => {
// Handle everything that was not previously handled
// Contrast to addMatcher which may handle an action
// that was already handled by other case / matchers
});
}
});
Reducer Creator
This alternative reducers creation allows you to add thunks without having to revert to extraReducers.
import { buildCreateSlice, asyncThunkCreator } from "@reduxjs/toolkit";
// Extra setup required for adding thunks to reducers
const createAppSlice = buildCreateSlice({
creators: { asyncThunk: asyncThunkCreator },
});
const creatorSlice = createAppSlice({
initialState: [],
reducers: create => ({
deleteEntity: create.reducer<{id: number}>((state, action) => {
return state.filter(x => x.id !== action.payload);
}),
addPartial: create.preparedReducer(
(todo: Partial<Todo>) => {
return {
payload: {
text: 'Default Text',
...todo,
}
}
},
(state, action) => {
state.push(action.payload);
}
),
// See above, requires buildCreateSlice()
getCreatures: create.asyncThunk(
async (params, thunkApi): Promise<MyEntity> => {
const res = await fetch(`/api/resource/${params.id}`);
return res.json();
}, {
pending: state => {
console.log('Getting entity');
},
rejected: (state, action) => {
console.log('Failed getting entity');
},
fulfilled: (state, action) => {
state.push(action.payload);
}
}
),
}),
});