DEV Community

bilal khan
bilal khan

Posted on

Advanced Redux

Handling Asynchronous Actions with Redux Toolkit and Thunks

In modern web applications, managing asynchronous actions like API calls is a common challenge. Redux Toolkit simplifies this process using thunks. In this guide, we’ll cover how to handle async logic, like fetching data from an API, and integrate it into your Redux flow.


What are Thunks?

A thunk is a function that delays the execution of another function. In Redux, it’s a middleware that allows you to write async logic before dispatching actions to the store.

Redux Toolkit includes createAsyncThunk, a utility that simplifies writing thunks for async operations.


Scenario: Fetching Data from an API

We’ll build an example to fetch a list of users from a public API and display it in a React app.


Step 1: Setting Up Redux with Async Thunks

Install Axios (or use fetch API):

npm install axios
Enter fullscreen mode Exit fullscreen mode

Create a Slice with Async Logic

import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import axios from 'axios';

// Async action
export const fetchUsers = createAsyncThunk('users/fetchUsers', async () => {
  const response = await axios.get('https://jsonplaceholder.typicode.com/users');
  return response.data;
});

// Slice
const usersSlice = createSlice({
  name: 'users',
  initialState: {
    users: [],
    status: 'idle', // 'idle' | 'loading' | 'succeeded' | 'failed'
    error: null,
  },
  reducers: {},
  extraReducers: (builder) => {
    builder
      .addCase(fetchUsers.pending, (state) => {
        state.status = 'loading';
      })
      .addCase(fetchUsers.fulfilled, (state, action) => {
        state.status = 'succeeded';
        state.users = action.payload;
      })
      .addCase(fetchUsers.rejected, (state, action) => {
        state.status = 'failed';
        state.error = action.error.message;
      });
  },
});

export default usersSlice.reducer;
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • createAsyncThunk: Handles async logic (API call) and generates pending, fulfilled, and rejected actions.
  • extraReducers: Updates state based on the lifecycle of the async thunk.

Step 2: Configure the Store

import { configureStore } from '@reduxjs/toolkit';
import usersReducer from './usersSlice';

export const store = configureStore({
  reducer: {
    users: usersReducer,
  },
});
Enter fullscreen mode Exit fullscreen mode

Step 3: Provide the Store

Wrap your app with the Provider component:

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { store } from './store';
import App from './App';

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);
Enter fullscreen mode Exit fullscreen mode

Step 4: Create a Component to Fetch and Display Data

import React, { useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { fetchUsers } from './usersSlice';

const UsersList = () => {
  const dispatch = useDispatch();
  const users = useSelector((state) => state.users.users);
  const status = useSelector((state) => state.users.status);
  const error = useSelector((state) => state.users.error);

  useEffect(() => {
    if (status === 'idle') {
      dispatch(fetchUsers());
    }
  }, [status, dispatch]);

  return (
    <div>
      <h1>Users List</h1>
      {status === 'loading' && <p>Loading...</p>}
      {status === 'failed' && <p>Error: {error}</p>}
      {status === 'succeeded' && (
        <ul>
          {users.map((user) => (
            <li key={user.id}>{user.name}</li>
          ))}
        </ul>
      )}
    </div>
  );
};

export default UsersList;
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • useEffect: Fetches users when the component mounts.
  • useSelector: Retrieves state (users, status, and error) from the Redux store.
  • useDispatch: Dispatches the fetchUsers thunk to fetch data.

Step 5: Run the App

Start the development server:

npm start
Enter fullscreen mode Exit fullscreen mode

You’ll see a loading indicator, followed by a list of users fetched from the API.


How It Works

  1. Thunk Execution:

    • The fetchUsers thunk sends an API request.
    • Redux Toolkit dispatches pending, fulfilled, or rejected actions based on the result.
  2. State Updates:

    • The extraReducers in the slice updates the state according to the action.
  3. React Integration:

    • useSelector subscribes components to Redux state.
    • useDispatch triggers the thunk to perform async operations.

Advantages of Redux Toolkit with Thunks

  1. Simplified Async Handling:

    • No need for custom middleware or boilerplate code.
    • Automatically generates action types for async thunks.
  2. Enhanced Readability:

    • createAsyncThunk abstracts complex async logic.
    • extraReducers provide a clear mapping of actions to state updates.
  3. Built-In DevTools Support:

    • Redux DevTools can trace async actions seamlessly.

Conclusion

Redux Toolkit’s createAsyncThunk makes handling async operations in Redux intuitive and efficient. In this example, we covered:

  • Fetching data from an API.
  • Managing the async state lifecycle (loading, success, error).
  • Integrating async logic with React components.

With this foundation, you can confidently build apps requiring robust state management and async workflows. Stay tuned for more advanced Redux concepts, including middleware and real-world integration with MERN stack applications!

Top comments (0)