Simplify Async State Management with Redux Toolkit
Tags: redux, react, javascript, state
Published: 2023-10-03 20:00:19

If you have any experience with Redux, you might be familiar with Redux Toolkit. Redux Toolkit aims to simplify some of the setup for the store and reducers, and simplifies immutable update logic. Although Redux requires a significant amount of boilerplate code, Redux Toolkit makes it easier to get up and running. If you haven't used Redux Toolkit before, I'd recommend giving it a shot.

###Installing Redux Toolkit

To begin, install Redux Toolkit in your project directory.

<code>npm install @reduxjs/toolkit</code>

###Configuring the Store

The store is an essential part of Redux. The store is where your application's state will be stored and managed. Now that we've installed Redux Toolkit, let's initialize the store. Create a file called `store.js` in the client directory of your project.

Let's configure the store like so:

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

const store = configureStore({
  reducer: {
    users: usersReducer
  },
});

export default store;

```
We are going to be creating and importing some reducers, and adding them to the reducer object in our store. Using `configureStore` allows us to combine multiple reducers in a single store.

###Creating the Users Slice

In Redux Toolkit, slices are small, self-contained pieces of your Redux store that include reducers, actions, and async thunks. Create a new file called `usersSlice.js` and define your `usersSlice`:

```js
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit"

//Reducer
const usersSlice = createSlice({
    name: "users",
    initialState: {
        status: "idle",
        entities: [],
        errors: []
    },
    reducers: {},
    extraReducers: {},
)

export default usersSlice.reducer;

```
Let's break this down a little bit. First, we are importing `createAsyncThunk` and `createSlice`. `createAsyncThunk` will allow us to create functions that can dispatch actions (thunks) throughout our app in order to update state. `createSlice` will allow us to create our usersSlice, which is home to our initial state and reducers, thereby responsible for maintaining and updating our User state.

###Creating Async Actions

Now that we have our usersSlice setup, let's create our first thunk. When the app loads or a user signs in, we might want to fetch all of the user objects from our database. To do that, we need to first define a thunk like so:

```js
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit"

export const fetchUsers = createAsyncThunk("users/fetchUsers", () => {
    return fetch("/users")
    .then((r) =>r.json())
})

//Reducer
const usersSlice = createSlice({
    name: "users",
    initialState: {
        status: "idle",
        entities: [],
        errors: []
    },
    reducers: {},
    extraReducers: {},
)

export default usersSlice.reducer;
```

Here, we created an action called fetchUsers, which is an async thunk. This thunk makes a fetch GET request to our proxy of "/users", and returns a response.

###Creating Extra Reducers

To make updates to state with the response from our thunk, we need to create some cases for extraReducers.

These cases look for the action type ("users/fetchUsers"), and whether that action is currently pending, fulfilled, or rejected. Depending on the case, we can update state accordingly. Let's update our usersSlice to look like this:

```js
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit"

export const fetchUsers = createAsyncThunk("users/fetchUsers", () => {
    return fetch("/users")
    .then((r) =>r.json())
})

//Reducer
const usersSlice = createSlice({
    name: "users",
    initialState: {
        status: "idle",
        entities: [],
        errors: []
    },
    reducers: {},
    extraReducers: (builder) => {
    builder
    //fetchUsers
        .addCase(fetchUsers.pending, (state) => {
            state.status = 'pending';
          })
          .addCase(fetchUsers.fulfilled, (state, action) => {
            state.status = 'fulfilled';
            state.entities = action.payload;
          })
          .addCase(fetchUsers.rejected, (state, action) => {
            state.status = 'rejected';
            state.errors = action.error.message;
          });
    },
)

export default usersSlice.reducer;
```

Here we've added 3 cases to the builder object which defines our reducers. State updates happen asynchronously when we make fetches to the database, so when the `fetchUsers` thunk is first dispatched, it enters a pending state, so we change our initial status to `pending`. If the request is successful, `fetchUsers.fulfilled` will run, thereby changing status to `fulfilled` and our entities array to the `action.payload` (an array of User objects returned from the database). If the request is unsuccessful, the `rejected` case will run, changing status to `rejected` and errors to `action.error.message`, which we can render to the page.

###Connecting Redux to React

To connect the Redux store to a React app, use the Provider component from react-redux. Wrap the app with the provider in the index.js file, and pass it the store as a prop:

```js
import ReactDOM from 'react-dom/client';
import App from './App';
import { Provider } from "react-redux";
import store from "./store"

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
   <Provider store={store}>
       <App />
   </Provider>
);
```

###Using Redux Toolkit in Components

Now that we have set up Redux Toolkit and the `usersSlice`, we can start using it in our React components.

####Fetching Users
To fetch users in a component, import the `fetchUsers` async thunk and use the `useDispatch` hook to dispatch the action:

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

const UserList = () => {
  const dispatch = useDispatch();
  const users = useSelector((state) => state.users.entities);
  const status = useSelector((state) => state.users.status);
  const errors = useSelector((state) => state.users.errors);

  useEffect(() => {
    dispatch(fetchUsers());
  }, [dispatch]);

  if (status === 'pending') {
    return <div>Loading...</div>;
  }

  if (errors) {
    return <div>Error: {error}</div>;
  }

  return (
    <div>
      <h2>User List</h2>
      <ul>
        {users.map((user) => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
    </div>
  );
};

export default UserList;
```

Here we are importing `useEffect` in order to dispatch our `fetchUsers` thunk/action when the app loads. We add `dispatch` to the dependencies array to prevent some error warnings.

We are also importing `useDispath` and `useSelector`. `useDispatch` allows us to dispatch thunks from our slices. `useSelector` allows us access to our slices' state, and it subscribes the component to that piece of state, so whenever it changes, the component will rerender with the state update.

As you can see, we are conditionally rendering on whether the fetch has completed or returned an error. Upon successful fulfillment, we fetch the user objects and map over them to render a list of user names to the page.

###Conclusion
Redux Toolkit, with its createAsyncThunk utility, simplifies handling asynchronous updates in your Redux application. By following the steps outlined in this article, you can create a "users" slice that efficiently fetches user data from an API and updates the state based on the fetch status.

With Redux Toolkit, you can focus on building features and user interfaces without getting bogged down by repetitive async action handling code. It's a valuable tool for streamlining your Redux development and ensuring smooth user experiences when dealing with async data.

In this article, we've explored how to use Redux Toolkit to manage asynchronous updates in a Redux store, specifically focusing on a "users" slice that fetches user data from an API. By following these steps, you can simplify the process of handling async actions and keep your Redux codebase clean and efficient.