Learning Redux Toolkit

Redux is a library for managing application state, commonly used together with React. There are multiple reasons why you would consider using Redux with React:

  1. Centralized State Management: Redux provides a centralized store that holds the entire state of our application. This makes it easier to manage and update our application's state in a predictable and consistent way.

  2. Predictable State Changes: Redux follows a strict pattern for updating the state of our application, called the Redux cycle. This makes it easier to understand and debug the behavior of our application.

  3. Time Travel Debugging: Redux provides a feature called time travel debugging, which allows you to step through the state changes of our application over time. This can be incredibly helpful for debugging complex applications.

  4. Easy to Test: Since Redux provides a centralized store for our application's state, it makes it easier to write tests for our application's state changes.

  5. Simplified Communication: Redux can simplify communication between different components of our application. Instead of passing state down through multiple levels of components, you can use Redux to manage state at a higher level and share that state between components.

Redux is often used with React to manage the state of complex applications and share the state with different indepedent components. With redux, you can create a single "store" that holds all of the required information. Each component of the app can then access and update this store as needed.

So, in summary, redux is a tool that helps you manage and update data across multiple components of an application, by providing a centralized store that all components can access and modify. This makes it easier to keep track of data and ensure that all components are working with the same up-to-date information.

Components of Redux Toolkit

In this article, we will learn step by step, how to implement Redux Toolkit. Let's undersatnd the primary components of redux Toolkit include - 

  1. createSlice: Helps us define a piece of our application's state and the actions that can update that state. It makes it easier to create redux reducers by automatically generating the necessary code based on our definition.

  2. configureStore: This helps us create a redux store with some preconfigured tools to handle certain actions. It also includes some extra tools to help you debug issues with our store.

  3. createAsyncThunk: This helps us define and handle asynchronous actions in our redux store. It generates the code needed to handle the different states of an asynchronous action, like loading, success, and error.

  4. createEntityAdapter: This allow us to manage collections of things, like users or products, in our store. It provides a simple way to add, update, and remove items from your collection.

Getting started with Redux Toolkit

To get started with Redux Toolkit, We will follow these steps:

  1. Install redux Toolkit in our project using a package manager like npm or yarn. 

  2. Define the redux slices using the createSlice function. This involves specifying the initial state of the slice, and the actions that can update that state.

  3. Create a redux store using the configureStore function. This involves passing in the redux slices, as well as any middleware that we want to use with our store.

  4. Use the Provider component from the react-redux library to wrap our React app, and pass in our redux store as a prop.

  5. Use the useSelector hook from react-redux to access the state in our redux store from our React components, and then use the useDispatch hook to dispatch actions to update that state.

First, we need to install - Redux Toolkit and React-Redux packages in our React project. Open the project and run the following command in the terminal -

npm install @reduxjs/toolkit react-redux

Directory Structure Example - 

main-app/
  node_modules/
  public/
    index.html
    favicon.ico
  src/
    components/
      App.js
    store/
      userSlice.js
      store.js
    index.js
  package.json

You can always customize the directory structure to fit your needs.

Let's assume we have 3 type of users for our application.

  • Admin, value = 0
  • Vendor, value = 1 and
  • Customer, value = 2

We are going to store the state of selected user type. Let's goto the userSlice.js first that resides in our store directory.

// userSlice.js

import { createSlice } from '@reduxjs/toolkit';

const userSlice = createSlice({
  name: 'user',
  initialState: {
    userType: null,
  },
  reducers: {
    setUserType: (state, action) => {
      state.userType = action.payload;
    },
  },
});

export const { setUserType } = userSlice.actions;

export default userSlice.reducer;

We have create our very first slice using createSlice function where have provided null for userType as initialState. 

The setUserType reducer takes in the current state object and an action object as parameters.

The setUserType reducer updates the userType property in the state object to the value of the payload property in the action object. The payload property is a convention used in redux to store the data that should be used to update the state.

So, whenever an action with a type of setUserType is dispatched in the redux store, this reducer will be called and it will update the userType property in the state with the new value specified in the payload property of the action object.

Next, the userSlice variable is an object that contains the name of the slice, the initialState of the slice, and a set of reducers that specify how to update the state in response to different actions. In our case, the 

export const { setUserType } = userSlice.actions

line exports the setUserType action creator function, which is generated automatically by the createSlice function. Action creators are functions that create action objects with a specific type and optional payload property.

Create Redux store 

The store.js file is typically the place where we define our redux store and import all the slices and other redux-related dependencies that we need to create our store. To configure our store, let's move to the store.js in our store directory. 

// store.js

import { configureStore } from '@reduxjs/toolkit';
import userReducer from './userSlice';

const store = configureStore({
  reducer: {
    user: userReducer,
  },
});

export default store;

In the above snippet, the userReducer imported from the userSlice file is used as the reducer for the user slice of the store. The store constant is created by calling configureStore with this options object, which creates a new store instance with the specified reducer and default middleware.

Connecting Redux store with React App

In order to provide the redux store to all the components in our app, we have to connect our redux store with our react application. To connect our main app with the redux store, we need the Provider component from the react-redux library at the root of our component hierarchy. Lets' connect it by opening index.js

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { BrowserRouter as Router, Route } from 'react-router-dom';
import store from './store/store';
import App from './components/App';
import HomePage from './components/HomePage';
import AboutPage from './components/AboutPage';

ReactDOM.render(
  <Provider store={store}>
    <Router>
      <App>
        <Route exact path="/" component={HomePage} />
        <Route path="/about" component={AboutPage} />
      </App>
    </Router>
  </Provider>,
  document.getElementById('root')
);

In the above snippet, we have wrapped our main app component. Note that, we also have Router under the Provider. Always make sure that the Provider component is wrapping all the components that need access to the Redux store.

Great! We have successfully created our slice, store and connected it with our react application.

Now let's create a simple component with input type select, from which when a user type is selected, disapatches the value of seletced user type to our store.

// App.js

import { useDispatch, useSelector } from 'react-redux';
import { setUserType } from './userSlice';

function SelectUserType() {
  const userType = useSelector((state) => state.user.userType);
  const dispatch = useDispatch();

  const handleUserTypeChange = (event) => {
    dispatch(setUserType(event.target.value));
  };

  return (
    <>
      <label>User Type:</label>
      <select value={userType} onChange={handleUserTypeChange}>
        <option value="0">Admin</option>
        <option value="1">Vendor</option>
        <option value="2">Customer</option>
      </select>
      <p>Selected User Type: {userType}</p>
    </>
  );
}

export default SelectUserType;

Now, when the user selects a user type from dropdown, the handleUserTypeChange function dispatches the setUserType action with the selected value and updates the userType value in the Redux store.

The userType value is retrieved from the Redux store using the useSelector hook, and the dispatch function is retrieved using the useDispatch hook.

That is all.

Now our all other independent compnents can share the userType when needed.

Let's assume we a have navigation component. We want to show the user what type of user is logged in, depending on the value selected from the dropdown. We can directly retrive the userType value from the store or we can create a small component - UserInfo and call it in our Navigation component.

Here is an exmaple on how to do this.

import React from "react";
import { useSelector } from "react-redux";


export default function UserTypeSelect() {
    const userType = useSelector((state) => state.user)

    return (
       <>Welcome {userType == 0 ? 'Admin' : userType == 1 ? 'Vendor' : userType == 2 ? 'Customer' : 'NA' }</>
    );
}

This is how Redux can be helpful for managing the state of your app.

When to use redux ?

It's not necessary to use it for every project.

We require state managment library when the app has a large or complex state that needs to be shared between multiple components or the application has multiple ways of updating the state, and you need to keep track of those changes in a centralized location.

If you think your app needs a tool for managing its state, then it might be a good idea to use Redux. However, if your app works fine without it, then you don't need to use Redux.