Redux-toolkit for efficient React development

Mindaugas Nakrosis
4 min readMar 5, 2021
Photo by Julie Molliver on Unsplash

I use Redux Toolkit package as a standard way of writing Redux logic. It was originally created to solve three common concerns about Redux:

  • “Redux requires too much boilerplate code”
  • “Configuring a Redux store is too complicated”
  • “I have to add a lot of packages to get Redux to do anything useful”

Redux Toolkit abstracts the setup process and handles most of the common use cases, as well as include some useful utilities that will let the user simplify their application code.

I believe these tools speed up the development process and are very intuitive and easy to use for all Redux users. It also simplifies your Redux application so it is more readable.

This is the API Redux Toolkit provides:

1. configureStore():

It wraps createStore to provide simplified configuration options and proper defaults. It can automatically combine your slice reducers, adds whatever Redux middleware you supply, includes redux-thunk by default, and has Redux DevTools Extension enabled by default.

import { configureStore } from '@reduxjs/toolkit'
import rootReducer from './reducers'
const store = configureStore({ reducer: rootReducer })

2. createReducer()

Lets you supply a lookup table of action types to case reducer functions, rather than writing switch statements. In addition, it automatically uses the immer library to let you write simpler immutable updates with normal mutative code, like state.todos[3].completed = true

Instead of writing default redux reducers like this:

const initialState = { value: 0 }function counterReducer(state = initialState, action) {
switch (action.type) {
case 'increment':
return { ...state, value: state.value + 1 }
case 'decrement':
return { ...state, value: state.value - 1 }
case 'incrementByAmount':
return { ...state, value: state.value + action.payload }
default:
return state
}
}

One of the ways (Builder callback) can be written like this:

import { createAction, createReducer } from '@reduxjs/toolkit'interface CounterState {
value: number
}
const increment = createAction('counter/increment')
const decrement = createAction('counter/decrement')
const incrementByAmount = createAction<number>('counter/incrementByAmount')
const initialState = { value: 0 } as CounterStateconst counterReducer = createReducer(initialState, (builder) => {
builder
.addCase(increment, (state, action) => {
state.value++
})
.addCase(decrement, (state, action) => {
state.value--
})
.addCase(incrementByAmount, (state, action) => {
state.value += action.payload
})
})

3. createAction()

Generates an action creator function for the given action type string. The function itself has toString() defined, so that it can be used in place of the type constant.

Instead of writing default redux actions like this:

const INCREMENT = 'counter/increment'function increment(amount: number) {
return {
type: INCREMENT,
payload: amount,
}
}
const action = increment(3)
// { type: 'counter/increment', payload: 3 }

You can write them like this:

import { createAction } from '@reduxjs/toolkit'const increment = createAction<number>('counter/increment')let action = increment()
// { type: 'counter/increment' }
action = increment(5)
// returns { type: 'counter/increment', payload: 5}

4. createSlice()

Most often used by me. Actions creators, actions constants, actions, reducers all combined into one file. Is it full of code and hardly readable? Hell no. Redux Toolkit automates most of the stuff, have a look yourself:

Photo by Marliese Streefland on Unsplash
import { createSlice, PayloadAction } from '@reduxjs/toolkit'interface DogState {
dogName: string
}
const initialState = { dogName: 'Doggo' } as DogStateconst dogSlice = createSlice({
name: 'dog',
initialState,
reducers: {
setName: (state, { payload }) => ({ dogName: payload }),
resetName: (state) => initialState,
},
})
export const { setName, resetName } = dogSlice.actions
export default dogSlice.reducer

5. createAsyncThunk

Particularly useful for setting data to state after async network requests.

// Create the thunk
const fetchDogById = createAsyncThunk(
'dogs/fetchByIdStatus',
async (dogId, thunkAPI) => {
const response = await dogsAPI.fetchById(dogId)
return response.data
}
)
// Then, handle actions in reducer:
const dogsSlice = createSlice({
name: ‘dogs’,
initialState: { dogs: [], loading: ‘idle’ },
reducers: {},
extraReducers: {
[fetchDogById.fulfilled]: (state, action) => {
state.dogs.push(action.payload)
}
}
})
// Dispatch the thunk
dispatch(fetchDogById(321))

6. createEntityAdapter

Automatically generates a chunk of reusable reducers and selectors to manage normalised data in the store. Useful for quick setup of standard data and operations with it. Helps to avoid bugs with imperfectly and custom written reducers.

7. createSelector

utility from the Reselect library, re-exported for ease of use. If you’re not sure for reselect does it prevents costly calculations in selectors if input parameters have not changed.

Thanks for reading!

Photo by Manuel Cosentino on Unsplash

--

--