How to Update Redux Thunk in React
This article provides a straightforward guide on how to update and
implement Redux Thunk in a React application. You will learn how to
transition from legacy, manual Redux Thunk configurations to the modern
Redux Toolkit standard, utilizing configureStore and
createAsyncThunk for efficient asynchronous state
management.
Step 1: Upgrade to Redux Toolkit
In legacy React applications, Redux Thunk had to be installed as an independent package and manually applied as middleware. The modern way to update and use Redux Thunk is by adopting Redux Toolkit (@reduxjs/toolkit). Redux Toolkit includes Redux Thunk by default, eliminating the need for manual middleware configuration.
To update your project, install the latest version of Redux Toolkit and React-Redux:
npm install @reduxjs/toolkit react-reduxStep 2: Update the Store Configuration
Historically, you configured Thunk using
applyMiddleware(thunk) inside Redux’s
createStore. With Redux Toolkit, you replace
createStore with configureStore. This
automatically enables the Redux Thunk middleware under the hood.
Here is how to update your store configuration:
import { configureStore } from '@reduxjs/toolkit';
import userReducer from './userSlice';
const store = configureStore({
reducer: {
user: userReducer,
},
});
export default store;Step 3: Define Asynchronous Logic with createAsyncThunk
Instead of writing manual creator functions that return a function,
use Redux Toolkit’s createAsyncThunk. This standardizes how
promises and asynchronous requests are handled.
createAsyncThunk accepts an action type string and a
payload creator callback function that returns a promise:
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
// The thunk handles the asynchronous request
export const fetchUserById = createAsyncThunk(
'user/fetchById',
async (userId) => {
const response = await fetch(`https://api.example.com/users/${userId}`);
return await response.json();
}
);Step 4: Handle Lifecycle Actions in a Slice
createAsyncThunk automatically generates action creators
for three promise lifecycles: pending,
fulfilled, and rejected. You must update your
slice reducer to handle these actions using the
extraReducers builder callback:
const userSlice = createSlice({
name: 'user',
initialState: { data: null, loading: false, error: null },
reducers: {},
extraReducers: (builder) => {
builder
.addCase(fetchUserById.pending, (state) => {
state.loading = true;
})
.addCase(fetchUserById.fulfilled, (state, action) => {
state.loading = false;
state.data = action.payload;
})
.addCase(fetchUserById.rejected, (state, action) => {
state.loading = false;
state.error = action.error.message;
});
},
});
export default userSlice.reducer;Step 5: Dispatch the Thunk in React Components
To trigger the updated Redux Thunk inside your functional React
components, use the useDispatch hook from
react-redux. Pass the payload argument directly into the
generated thunk function:
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { fetchUserById } from './userSlice';
const UserProfile = ({ userId }) => {
const dispatch = useDispatch();
const { data, loading, error } = useSelector((state) => state.user);
useEffect(() => {
dispatch(fetchUserById(userId));
}, [dispatch, userId]);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error}</p>;
return data ? <div><h1>{data.name}</h1></div> : null;
};
export default UserProfile;