Ditch redux, Use reduxjs/toolkit

Redux is a state management for different libraries like Vue, Angular, React and even vanilla JavaScript, etc. For the past few years, people and companies are considering Redux as their first and foremost priority library for managing states inside of a web application.

Why Redux?

It mainly gained its fame for particularly two things:

Redux helps you write applications that behave consistently, run in different environments (client, server, and native), and are easy to test.

That means no unintentional re-rendering of the components and no prop drilling to child components.

Centralizing your application's state and logic enables powerful capabilities like undo/redo, state persistence, and much more.

Change the state of the application from any of the child components no matter how deep they are nested.

But for gaining so much power, we need to take some precautions. Those are:

  • Do Not Mutate State

We will never mutate the state, no matter what our needs are. Instead, we can take snapshots and manipulate that. That means we can't use functions like array.push().

  • Only Store Per-App

We will never have more than one global store.

  • The Reducer Will Always be Pure Functions And Must Not Have Any Side Effects

We will never always return an object from the reducers and it will never perform asynchronous tasks. Later, this problem is solved by redux-thunk which checks, executes any async functions, and returns a plain object from that response.

Like all things, it has pros and cons too.

As with the pros, redux gained fame for being one of the best state management libraries, but lately, it was very difficult to maintain because of the boilerplate as we have to write all the reducers, action constants, action creators, and the store. Also, there are a lot of files for each collection of reducers.

Cons of redux:

  • In a very large web app, where we have to fetch data constantly from an API, it's not always easy to add those to existing data without accidentally mutating the state.

  • Boilerplate codes

Action Constants

const ADD_TODO = 'ADD_TODO';

Action Creators

const addTodo = (title) => {
  return {
           type: ADD_TODO,
           payload: title,
         }
}

Reducers Without Mutating State

const todoReducer = (state=[],action) => {
  switch(action.type){
    case ADD_TODO:
      return [action.payload,...state];
    default:
      return state;
  }
}

Combine Reducers

rootReducer = combineReducers({todo: todoReducer})

For some simple tasks we needed to keep a lot of stuff in mind and on top of that we had to follow a massive boilerplate.

Reduxjs/Toolkit aka Redux Starter Kit

At the end of 2019, taking people's opinion on the problem of managing a redux application is a big challenge the developers came out with Redux Starter Kit which after named as reduxjs/toolkit which was supposed to solve the problems we were having with default redux applications.

Includes utilities to simplify common use cases like store setup, creating reducers, immutable update logic, and more.

We no longer need to write out action creators (apart from thunk functions) and actions constants, it will be generated by the toolkit itself.

Takes inspiration from libraries like Immer and Autodux to let you write "mutative" immutable update logic, and even create entire "slices" of state automatically.

Finally, we can mutate the state (not really). Redux uses Immer in the background to convert our mutate code to immutable code.

Provides good defaults for store setup out of the box, and includes the most commonly used Redux addons built-in.

No longer do we need to add packages like redux-thunk and redux-devtools as it is already included in the toolkit.

It solves most of the problems and made redux much easier to write.

Redux Toolkit gives us a hook createSlice() in which we can mention our action name, initials state, reducers and the state for async data fetching i.e pending, fulfilled, or rejected.

import { createSlice } from '@reduxjs/toolkit'

const initialState = []

const todoSlice = createSlice({
  name: 'todo',
  initialState,
  reducers: {
    addTodo: (state,action) => {
      state.push(action.payload)
    },
  },
})

export const todoActions = todoSlice.actions
export default todoSlice.reducer

We wrote the code that would have took 2-3 files in redux is just finished in one file with not more than 15 lines of code. Also, we mutated the state but redux didn't throw any error as it is being converted to immutable code in the background.

Async Data Fetch

Reduxjs/toolkit also provides a hook createAsyncThunk() for async thunk functions which we have to manually export action.

const fetchUserById = createAsyncThunk(
  'users/fetchByIdStatus',
  async (userId, thunkAPI) => {
    const response = await userAPI.fetchById(userId)
    return response.data
  }
)

We can trigger some side-effects when the request is pending, rejected or fulfilled

const usersSlice = createSlice({
  name: 'users',
  initialState: { entities: [], loading: 'idle' },
  reducers: {
    // standard reducer logic, with auto-generated action types per reducer
  },
  extraReducers: {
    [fetchUserById.rejected]:(state,action) => {
      //Some functions or notifications if there is an error
    },
    [fetchUserById.pending]:(state,action) => {
      //Some functions or notifications if req is going through
    },
    [fetchUserById.fulfilled]:(state,action) => {
      //Some functions or notifications is res is back as successful
    },
  },
})

So, we can say reduxjs/toolkit is a major improvement over the default redux.

20