We can write thunks that dispatch “loading”, “request succeeded”, and “request failed” actions. We had to write action creators, action types, and reducers to handle those cases.
Because this pattern is so common, Redux Toolkit has a createAsyncThunk
API that will generate these thunks for us. It also generates the action types and action creators for those different request status actions, and dispatches those actions automatically based on the resulting Promise
.
booksSlices.ts
import {createAsyncThunk, createEntityAdapter, createSlice} from "@reduxjs/toolkit";
import {RootState} from "../../app/store";
export type BookState = { bookId: number, bookTitle: string, };
export const addOneBookAsync = createAsyncThunk(
"books/addOneBookAsync",
async (bookTitle: string) => {
let result;
setTimeout(result = function () {
const newBook: BookState = {bookId: Math.floor(Math.random() * 1000), bookTitle: bookTitle};
return newBook;
}, 1000);
return result();
}
);
const booksAdapter = createEntityAdapter<BookState>({
selectId: model => model.bookId,
sortComparer: (a, b) => a.bookTitle.localeCompare(b.bookTitle),
});
export const booksSlice = createSlice({
name: "books",
initialState: booksAdapter.getInitialState,
reducers: {
addOne: booksAdapter.addOne,
},
extraReducers: builder => {
builder
.addCase(addOneBookAsync.pending, state => {
// we can update state while loading data
console.log("loading");
})
.addCase(addOneBookAsync.fulfilled, (state, action) => {
// we can update state after successful response or we can call another reducer
// here we are going to call another reducer
booksSlice.caseReducers.addOne(state, action);
console.log("successful");
}).addCase(addOneBookAsync.rejected, state => {
// we can update state after failed
console.log("failed");
})
}
});
export const booksActions = booksSlice.actions;
export const booksSelectors = booksAdapter.getSelectors<RootState>(
(state) => state.books
)
export default booksSlice.reducer;
Books.tsx
import {useAppSelector, useAppDispatch} from '../../app/hooks';
import {BookState, booksActions, booksSelectors, addOneBookAsync} from "./booksSlice";
import {useState} from "react";
function Books() {
const dispatch = useAppDispatch();
const books = useAppSelector(booksSelectors.selectAll);
const [bookTitle, setBookTitle] = useState("");
const addHandler = () => {
const newBook: BookState = {
bookId: Math.floor(Math.random() * 1000),
bookTitle: bookTitle
};
dispatch(booksActions.addOne(newBook));
}
const addAsyncHandler = async () => {
try {
const result = await dispatch(addOneBookAsync(bookTitle)).unwrap();
console.log(result);
} catch (rejectedValueOrSerializedError) {
// handle error here
}
}
return (
<div>
<div className="mx-2 my-2">
<input type="text" placeholder="Books Title" className="input input-bordered w-full max-w-xs"
value={bookTitle} onChange={event => setBookTitle(event.target.value)}/>
<button className="btn mx-2" onClick={addHandler}>Add</button>
<button className="btn mx-2" onClick={addAsyncHandler}>Add Async</button>
</div>
<ul>
{books.map((value, index) => (
<li key={value.bookId}>Title : {value.bookTitle}, Id : {value.bookId}</li>
))}
</ul>
</div>
);
}
export default Books;
Return Value
createAsyncThunk
returns a standard Redux thunk action creator. The thunk action creator function will have plain action creators for the pending
, fulfilled
, and rejected
cases attached as nested fields.
Handling Thunk Results
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:
const onClick = () => {
dispatch(fetchUserById(userId))
.unwrap()
.then((originalPromiseResult) => {
// handle result here
})
.catch((rejectedValueOrSerializedError) => {
// handle error here
})
}
Or with async/await syntax:
const onClick = async () => {
try {
const originalPromiseResult = await dispatch(fetchUserById(userId)).unwrap()
// handle result here
} catch (rejectedValueOrSerializedError) {
// handle error here
}
}
References
https://redux-toolkit.js.org/api/createAsyncThunk
https://redux.js.org/tutorials/fundamentals/part-8-modern-redux#writing-thunks
https://redux-toolkit.js.org/api/createslice#extrareducers
https://stackoverflow.com/questions/65106681/redux-toolkit-dispatch-an-action-from-an-extrareducers-listener