Learnitweb

Redux Toolkit example

1. Introduction

In this tutorial we’ll rewrite our Redux example we created earlier with Redux Toolkit.

2. Configure Store with Redux Toolkit

The configureStore API from Redux Toolkit creates a Redux store that holds the complete state tree of your app, and also automatically configure the Redux DevTools extension so that you can inspect the store. The configureStore API takes 3 arguments:

  • reducer: A root reducer function that returns the next state tree, given the current state tree and an action to handle.
  • preloadedState: The initial state. This is optional.
  • enhancer: The store enhancer. This is optional.

Here is our store.js without Redux Toolkit.

import { legacy_createStore as createStore, applyMiddleware } from "redux";

import reducer from "./reducer";

const store = createStore(reducer);
export default store;

The store.js can be rewritten with Redux Toolkit as.

import { configureStore } from "@reduxjs/toolkit";
import reducer from "./reducer";

const store = configureStore({ reducer: reducer });
export default store;

3. Defining actions with Redux Toolkit

The createAction is a helper function for defining a Redux action type and creator. Typically, in Redux, you define an action by separately declaring a constant for the action type and an action creator function that constructs actions of that type. The createAction helper streamlines the process by merging these two declarations into a single step. It accepts an action type and returns an action creator for that type. This action creator can be invoked with or without arguments, attaching a payload to the action if provided.

Here is our code without Redux Toolkit.

import * as actionTypes from "./actionTypes";
export const addTask = (task) => {
  return { type: actionTypes.ADD_TASK, payload: { task: task } };
};
export const removeTask = (id) => {
  return { type: actionTypes.REMOVE_TASK, payload: { id: id } };
};
export const completedTask = (id) => {
  return { type: actionTypes.TASK_COMPLETED, payload: { id: id } };
};

Here is the code with Redux Toolkit.

import { createAction } from "@reduxjs/toolkit";

export const addTask = createAction("ADD_TASK");
export const removeTask = createAction("REMOVE_TASK");
export const taskCompleted = createAction("TASK_COMPLETED");

You need to change the reducers as well. If our reducer, we had switch case:

Here, is the code without Redux Toolkit:

switch(action.type) {
	case actionTypes.ADD_TASK:
		.............
	case actionTypes.REMOVE_TASK:
		.............
	case actionTypes.TASK_COMPLETED:
		.............
}

This can be written with Redux Toolkit as:

switch(action.type) {
	case addTask.type`:
		 .............
	case removeTask.type:
		 .............
	case taskCompleted.type:
		 .............
}

In the index.js, you need to pass the payload.

Here is the code snippet from index.js.

store.dispatch(addTask("Task 1"));
store.dispatch(removeTask(1);

Here is the same code with Redux Toolkit.

store.dispatch(addTask({ task: "Task 1" }));
store.dispatch(removeTask({ id: 2 }));

4. Creating Reducer with Toolkit

The createReducer() is a utility that simplifies creating Redux reducer functions. Internally, it employs Immer to significantly simplify immutable update logic, allowing you to write “mutative” code within your reducers. Additionally, it supports the direct mapping of specific action types to case reducer functions, which will update the state when the corresponding action is dispatched.
Here is the code of reducer.js without using Redux Toolkit:

import * as actionTypes from "./actionTypes";
let id = 0;
export default function reducer(state = [], action) {
  switch (action.type) {
    case actionTypes.ADD_TASK:
      return [
        ...state,
        {
          id: ++id,
          task: action.payload.task,
          completed: false,
        },
      ];
    case actionTypes.REMOVE_TASK:
      return state.filter((task) => task.id != action.payload.id);
    case actionTypes.TASK_COMPLETED:
      return state.map((task) =>
        task.id === action.payload.id
          ? {
              ...task,
              completed: true,
            }
          : task
      );
    default:
      return state;
  }
}

Here is the code of reducer.js with Redux Toolkit.

import { addTask, removeTask, taskCompleted } from "./action";
import { createReducer } from "@reduxjs/toolkit";
let id = 0;

export default createReducer([], (builder) => {
  builder.addCase(addTask, (state, action) => {
    state.push({
      id: ++id,
      task: action.payload.task,
      completed: false,
    });
  }),
    builder.addCase(removeTask, (state, action) => {
      const index = state.findIndex((task) => task.id == action.payload.id);
      state.splice(index, 1);
    }),
    builder.addCase(taskCompleted, (state, action) => {
      const index = state.findIndex((task) => task.id == action.payload.id);
      state[index].completed = true;
    });
});

5. Combine action and reducer with createSlice

A function that accepts an initial state, an object of reducer functions, and a “slice name”, and automatically generates action creators and action types that correspond to the reducers and state. Internally, it uses createAction and createReducer, so you may also use Immer to write “mutating” immutable updates.

Here is the code of taskSlice.

import { createSlice } from "@reduxjs/toolkit";
let id = 0;

const taskSlice = createSlice({
  name: "tasks",
  initialState: [],
  reducers: {
    addTask: (state, action) => {
      state.push({
        id: ++id,
        task: action.payload.task,
        completed: false,
      });
    },
    removeTask: (state, action) => {
      const index = state.findIndex((task) => task.id == action.payload.id);
      state.splice(index, 1);
    },
    taskCompleted: (state, action) => {
      const index = state.findIndex((task) => task.id == action.payload.id);
      state[index].completed = true;
    },
  },
});

export const { addTask, removeTask, taskCompleted } = taskSlice.actions;
export default taskSlice.reducer;

6. Combining reducers with redux-toolkit

Suppose there are two reducers: employeeReducer and taskReducer. Our store can be configured to use these two reducers.

import { configureStore } from "@reduxjs/toolkit";
import employeeReducer from "./employees";
import taskReducer from "./tasks";

const store = configureStore({
  reducer: {
    tasks: taskReducer,
    employees: employeeReducer,
  },
});
export default store;

7. Conclusion

In this tutorial, we updated our earlier example of Redux with Redux Toolkit.