DEV Community

Cover image for 🌀 Redux: Using createSlice and createAsyncThunk for Cleaner State Management
Sivasubramaniyam
Sivasubramaniyam

Posted on • Updated on

🌀 Redux: Using createSlice and createAsyncThunk for Cleaner State Management

React Learning Guide

State management in React applications can be challenging, especially when dealing with complex data flows and asynchronous operations. Redux has long been a popular choice for managing state, but it came with boilerplate and complexity. Enter Redux Toolkit, the official, recommended way to write Redux logic. It provides a powerful yet easy-to-use API that simplifies common tasks like creating reducers, actions, and handling side effects.

In this article, we will explore createSlice and createAsyncThunk from Redux Toolkit, demonstrating how they help manage both synchronous and asynchronous state changes with minimal code.

Why Redux Toolkit?

Redux Toolkit addresses many pain points of traditional Redux:

  • Less boilerplate: createSlice lets you define the initial state, reducers, and actions all in one place.
  • Built-in Thunks: createAsyncThunk provides a streamlined way to handle async operations like API calls.
  • Immutability and Immer: Redux Toolkit uses Immer under the hood, allowing us to write "mutative" logic in reducers that is safely converted to immutable updates.

Getting Started

Before we dive into code, make sure you have Redux Toolkit installed:

npm install @reduxjs/toolkit react-redux
Enter fullscreen mode Exit fullscreen mode

Example: Fetching Data with createAsyncThunk and createSlice

In this example, we'll create a simple slice of state that handles fetching data from an API. We'll use createAsyncThunk to handle the asynchronous data fetching and createSlice to manage the state and reducers.

Step 1: Setting Up createSlice and createAsyncThunk

Let's create a new slice to manage a list of posts:

// src/features/posts/postsSlice.js
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";

// Step 1: Create an async thunk for fetching posts
export const fetchPosts = createAsyncThunk(
  "posts/fetchPosts",
  async (_, thunkAPI) => {
    try {
      const response = await fetch("https://jsonplaceholder.typicode.com/posts");
      const data = await response.json();
      return data;
    } catch (error) {
      return thunkAPI.rejectWithValue({ error: error.message });
    }
  }
);

// Step 2: Create a slice
const postsSlice = createSlice({
  name: "posts",
  initialState: {
    posts: [],
    status: "idle", // 'idle' | 'loading' | 'succeeded' | 'failed'
    error: null,
  },
  reducers: {
    // You can define synchronous reducers here if needed
  },
  extraReducers: (builder) => {
    builder
      .addCase(fetchPosts.pending, (state) => {
        state.status = "loading";
      })
      .addCase(fetchPosts.fulfilled, (state, action) => {
        state.status = "succeeded";
        state.posts = action.payload;
      })
      .addCase(fetchPosts.rejected, (state, action) => {
        state.status = "failed";
        state.error = action.payload.error;
      });
  },
});

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

Step 2: Setting Up the Redux Store

Now that we have our slice, let's set up the Redux store and add the posts reducer:

// src/app/store.js
import { configureStore } from "@reduxjs/toolkit";
import postsReducer from "../features/posts/postsSlice";

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

Step 3: Using the Slice in a Component

Now, let's use this slice in a component to fetch and display the posts:

// src/components/PostsList.js
import React, { useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import { fetchPosts } from "../features/posts/postsSlice";

const PostsList = () => {
  const dispatch = useDispatch();
  const posts = useSelector((state) => state.posts.posts);
  const status = useSelector((state) => state.posts.status);
  const error = useSelector((state) => state.posts.error);

  useEffect(() => {
    if (status === "idle") {
      dispatch(fetchPosts());
    }
  }, [status, dispatch]);

  if (status === "loading") {
    return <div>Loading...</div>;
  }

  if (status === "failed") {
    return <div>Error: {error}</div>;
  }

  return (
    <div>
      <h2>Posts</h2>
      {posts.map((post) => (
        <div key={post.id}>
          <h3>{post.title}</h3>
          <p>{post.body}</p>
        </div>
      ))}
    </div>
  );
};

export default PostsList;
Enter fullscreen mode Exit fullscreen mode

Step 4: Integrating the Component in Your App

Finally, wrap your main component with Provider to connect the Redux store:

// src/App.js
import React from "react";
import { Provider } from "react-redux";
import { store } from "./app/store";
import PostsList from "./components/PostsList";

function App() {
  return (
    <Provider store={store}>
      <div className="App">
        <PostsList />
      </div>
    </Provider>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Recap

In this example, we covered the following:

  • Using createAsyncThunk to handle asynchronous actions like API calls.
  • Using createSlice to define a slice of state, reducers, and actions.
  • Setting up a Redux store using configureStore.
  • Connecting Redux state to a React component using useSelector and useDispatch.

Redux Toolkit makes it simpler to write Redux logic, reducing boilerplate and adding helpful utilities. With createAsyncThunk, we can easily manage complex async workflows, while createSlice allows us to define reducers and actions in one place.

Conclusion

Redux Toolkit significantly simplifies state management in React applications. By using createSlice and createAsyncThunk, you can handle both synchronous and asynchronous state updates in a more manageable way. Whether you're a beginner or an experienced developer, Redux Toolkit's streamlined API helps you build scalable and maintainable applications.

Top comments (0)