What is a Thunk?
In programming, the word Blow Refers to the portion of code that performs deferred work, such as an asynchronous function in JavaScript.
Redux storage itself does not handle asynchronous logic. It only knows how to:
- Schedule actions synchronously.
- Update status via reducer.
- Notify the UI about state changes.
But wait, if that’s the case, how do we call the API and update the status based on their response, which usually takes time? How should we deal with this?
This is where the thunk function comes into play.
What is the Thunk function?
Thunk functions are functions built to handle non-synchronous logic (such as calling APIs). It takes two parameters dispatch
and getState
Schedule operations and access current state when needed.
const getAllUsers = () => 'failed'
error: null,
The function returned is a thunk function and getAllUsers
Called the thunk action creator in this case, it will be dispatched like this:
dispatch(getAllUsers())
If needed, the thunk operation creator can be scheduled using the parameters used in the thunk function.
Create a thunk using the following command createAsyncThunk
The Redux toolkit provides createAsyncThunk
API to easily generate thunks:
import
state.status = 'failed';
state.error = action.error.message from '@reduxjs/toolkit';
export const fetchUserById = createAsyncThunk(
'user/fetchUserById',
async (userId) =>
);
fetchUserById
is the thunk function created here. createAsyncThunk
There are two parameters:
- The first argument is a string prefix for the type of operation to be produced (e.g.
user/fetchUserById/pending
,user/fetchUserById/fulfilled
oruser/fetchUserById/rejected
). - The second parameter is the “payload creator” function. It should return a Promise with the required data or an error.
Why use createAsyncThunk
?
In addition to letting you create thunk functions for API calls, createAsyncThunk
Automatically schedule operations to track the status of API requests:
-
pending
: The request is in progress. -
fulfilled
: The request was successful. -
rejected
: The request failed.
This is really useful. For example, we can show the loader in the UI when the status is pending
and let users know something is going on.
Using thunks in slices
Now we have created fetchUserById
thunk, we can use extraReducers
our areas userSlice
Handling status changes:
import
user: null,
status: 'idle', // 'idle' from '@reduxjs/toolkit';
const initialState = 'Something went wrong.';
;
export const userSlice = createSlice({
name: 'user',
initialState,
reducers: {
usernameUpdated: (state, action) => {
state.user.username = action.payload;
},
emailUpdated: (state, action) => {
state.user.email = action.payload;
},
userDataCleared: (state) => {
state.user = null;
state.status = 'idle';
},
},
extraReducers: (builder) => {
builder
.addCase(fetchUserById.pending, (state) => {
state.status = 'pending';
})
.addCase(fetchUserById.fulfilled, (state, action) => {
state.status = 'succeeded';
state.user = action.payload;
})
.addCase(fetchUserById.rejected, (state, action) => {
state.status = 'failed';
state.error = action.error.message || 'Something went wrong.';
});
},
});
export const { usernameUpdated, emailUpdated, userDataCleared } = userSlice.actions;
// Selector for the status to use in the application's components
export const selectStatus = (state) => state.user.status;
createAsyncThunk
situation
What if we want to check some conditions before calling the API? For example, we don’t want to call it twice if the state is already pending. In this case we can use the third parameter createAsyncThunk
Accept the writing conditions.
export const fetchUserById = createAsyncThunk(
"user/fetchUserById",
async (userId) => {
const response = await someHttpRequest(userId);
return response;
},
{
condition(_, { getState }) {
const status = selectStatus(getState());
if (status !== "idle") {
return false;
}
},
}
);
To learn how to use Typescript with thunk functions, read Type checking Redux thunk.