DEV Community

Cover image for Experience the Power of Redux Toolkit
Kiran D Patkar
Kiran D Patkar

Posted on

Experience the Power of Redux Toolkit

Introduction:

Redux Toolkit (RTK) is a set of utilities for Redux, a popular state management library for JavaScript applications. It provides a set of conventions and utilities that make it easier to build Redux applications by reducing the amount of boilerplate code you need to write. It structures our Redux code in a way that is easier to understand and maintain

Why Redux Toolkit?

The problem with vanilla redux is it's having a lot of boilerplate code, which most of the developers don't want to do, we have to write action creators again and again in vanilla redux. Also, it requires more libraries to make it more effective
Redux toolkit is a reincarnation of redux, it makes the developer's life so easy.

The reason for preferring the Redux toolkit:

  1. Less boilerplate code compared to vanilla redux
  2. No need to do extra setup for asynchronous operations,it has createAsyncThunk which will take care of it
  3. It's easy to understand, we can achieve the same functionality that vanilla redux provides in a very short and sweet way.

Since 2019 Redux Toolkit is the officially recommended approach to write any Redux code.

Installation:

We need to install two packages

npm install @reduxjs/toolkit react-redux

       or

yarn add @reduxjs/toolkit react-redux
Enter fullscreen mode Exit fullscreen mode

For creating a new React based Project with a Redux toolkit pre-installed

npx create-react-app my-app --template redux
Enter fullscreen mode Exit fullscreen mode

Setup store

In Redux, a store is an object that holds the application's state.
It provides a way to access, update, and monitor the state. To create the store we have to import the configureStore function from the Redux toolkit.

Before configuring the store we have to create a redux reducer function that will specify how the state should be updated depending upon the actions triggered. Reducer function will take two arguments current state and action, it returns a new state depending upon the action.

Step 1: Implement createSilce method and export actions and reducer.

In Redux, a slice is a piece of the application's state that is managed by a specific reducer. Redux Toolkit provides the createSlice utility for creating slices of state and the corresponding reducer and action creators. createSlice takes an object that specifies the initial state, reducer functions, and action creators for the slice.

import { createSlice } from '@reduxjs/toolkit';
const todoListSlice = createSlice({
    name: "todoList",
    initialState: [],
    reducers: {
        addTodo: (state, action) => {
          state.push(action.payload);
    },
        removeTodo: (state, action) => {
          state.filter(todo => todo.id !== action.payload);
    }
    }
});
export const { addTodo, removeTodo } = todoListSlice.actions;
export default todoListSlice.reducer;
Enter fullscreen mode Exit fullscreen mode

Step 2: Add Slice Reducers to the Store

In Redux, the configureStore function is a utility function provided by the redux-toolkit package that helps you set up a Redux store for your application. It takes a single argument, an options object, and returns a configured Redux store object.

import { configureStore } from '@reduxjs/toolkit';
import { combineReducers } from 'redux';
import todoList from 'todoListSlice';

const rootReducer = combineReducers({todoList}) which is similar to
const rootReducer = combineReducers({todoList:todoList})

If we want to use different attribute name then we can write like this
const example=combineReducers({attributeName:reducerName})

const store = configureStore({ devTools: true, reducer: rootReducer });
export default store;
Enter fullscreen mode Exit fullscreen mode

We have to pass a single reducer function to our store, as the app grows more complex, you'll want to split your reducing function into separate functions, each managing independent parts of the state.
The combineReducers helper function turns an object whose values are different reducing functions into a single reducing function.
for ex: const rootReducer=combineReducers({reducer1,reducer2,....,reducer-N})

The configureStore function accepts several options that allow you to customize the behavior of the store. Some common options include:

  • reducer: The root reducer of our application

  • devTools: This will allow us to inspect the state of our store

  • preloadedState: The initial state of the store.

Other than this there are many other options are vailable for configureStore. If you want more information about configureStore you can read the documentation here

Step 3: Connect our store to the app

Now we need to connect our store to the app. This will make our app access the redux store that we have created.

import { Provider } from 'react-redux';
import store from './redux/store';
.....
 <Provider store={store}>
        <App />
 </Provider>
.....
Enter fullscreen mode Exit fullscreen mode

Step 4: Read and update the global state using useSelector() and useDispatch() hooks

By using useSelector and useDispatch from react-redux, we can read the state from a Redux store and dispatch any action from a component, respectively.

useSelector():

useSelector is a function that takes the current state as an argument and returns whatever data you want from it.

import { useSelector } from 'react-redux';

const list = useSelector((state) => state.todoList);

// Here todoList is the attribute name given to the reducer 


Enter fullscreen mode Exit fullscreen mode

useDispatch():

If we want to modify the global state we need to use useDispatch and the action that we already created in slice.

import React, { useState } from 'react';
import { useDispatch } from 'react-redux';
import { addTodo } from 'todoListSlice';

export default const MyTodoList=()=> {
  const [text, setText] = useState('');
  // We will invoke useDispatch and store it to a variable
  const dispatch = useDispatch();

  const addTodo = (e) => {
    e.preventDefault();
// Here we are updating the todoList state by making use of the action that we have created in todoList slice
    dispatch(addTodo(text));
    setText('');
  };

  return (
    <form onSubmit={addTodo}>
      <input
        type='text'
        value={text}
        onChange={(e) => setText(e.target.value)}
      />
      <button>Add todo</button>
    </form>
  );
}
Enter fullscreen mode Exit fullscreen mode

If we want to remove the list from todoList we have to dispatch removeTodo action.

Handling asynchronous action in Redux toolkit

To perform asynchronous logic before sending the processed result to the reducers we have to make use of createAsyncThunk which is provided by toolkit

import { createSlice } from '@reduxjs/toolkit'
  const asynchronousSlice = createSlice({
  name: 'async',
  initialState={
   isLoading:false,
   data:[],
   isSuccess:false,
   message:""
},
  reducers: {},
  extraReducers: {},
})

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

Within createSlice,synchronous requests are handled by reducer object and asynchronous requests are handled by extraReducer.

import { createAsyncThunk } from '@reduxjs/toolkit'
export const getData=createAsyncThunk("async/fetch",async(arg,thunkApi)=>{
const response=await fetch('url')
return response.data

})
Enter fullscreen mode Exit fullscreen mode

This will create an action with async/fetch. The action will also have three additional properties pending, fulfilled, and rejected, which can be used to track the status of the async action. Depending upon this we can show a loading screen in UI to make it more user friendly

import { createSlice } from '@reduxjs/toolkit'
  const asynchronousSlice = createSlice({
  name: 'async',
  initialState={
   isLoading:false,
   data:[],
   isSuccess:false,
   message:""
},
  reducers: {},
  extraReducers: {

 [getData.pending]: (state) => {
      state.isLoading = true
    },
 [getData.fulfilled]: (state, action) => {
      state.isLoading = false
      state.data = action.payload
      state.isSuccess=true
    },
 [getData.rejected]: (state, action) => {
      state.isLoading = false
      state.message = action.error
    }

},
})

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

Let's setup a component which will dispatch getData when it mounts.

import { useEffect } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { getData } from 'asynchronousSlice'

export default function Home() {
  const dispatch = useDispatch()
  const { data, isLoading } = useSelector((state) => state.async)

  useEffect(() => {
    dispatch(getData())
  }, [])

  if (loading) return <p>Loading...</p>

  return (
    <div>
      <h2>Details</h2>
      {data.map((post) => (
        <p key={post.id}>{post.title}</p>
      ))}
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

Conclusion and Thoughts

Redux Toolkit can be a useful tool for reducing the amount of code you have to write when working with Redux, and for helping you structure your code in a way that is easier to understand and maintain.

Thanks for reading this article, and I hope you enjoyed reading it and learned something about Redux toolkit. Please leave a comment if you have any questions for me

Latest comments (0)