GETTING STARTED WITH REDUX-TOOLKIT.

Rose Sumba
5 min readFeb 15, 2022
Redux-toolkit header image

When it comes to state management in React, Redux has most probably been the preferred way by many developers. However, there is another library gaining popularity due to its ease of usage and lightweight configuration — Redux-Toolkit.

If you are a developer getting started with redux, you may wonder, what exactly is redux-toolkit, why is it simple to use, or what makes it different from the standard redux. In this article, I am going to highlight the benefits of using redux-toolkit over redux and show how to set it up in your development projects.

So, why Redux-toolkit?

  • Configuring the store is quite simple. Just a few lines of code and you are ready to go. In redux, especially when you have asynchronous code, it can be a pain in the neck due to the number of packages you need to get started.
  • Reduced amount of boilerplate code needed to manage your state. For example, this is as opposed to redux where you need to repeatedly write action creators and define action types to be used in several places as we will see later in this article.

Setup

Run npm install @reduxjs/toolkit or yarn add @reduxjs/toolkitto add the redux-toolkit package to your node modules.

Configuring the store.

Setting the store in redux involves the following:

  • Importing or creating the root reducer function
  • Setting up middleware, likely including at least one middleware to handle asynchronous logic
  • Configuring the Redux DevTools Extension
import { createStore, applyMiddleware } f 'redux';
import thunk, { ThunkMiddleware } from 'redux-thunk';
import { composeWithDevTools } from 'redux-devtools-extension';
import rootReducer from './reducers';
const store = createStore(rootReducer,composeWithDevTools(applyMiddleware(thunk as ThunkMiddleware)));export default store;

Whereas in redux-toolkit, all you need is theconfigureStore which adds some middleware by default. Next, pass the root reducer function as a parameter named reducer . And that’s it, simple, neat, and easy to read.

import { configureStore } from '@reduxjs/toolkit'
import rootReducer from './reducers'

const store = configureStore({
reducer: rootReducer,
})

export default store

The configureStore will call combineReducers for you when you manually add the object of “slice reducers” as its argument.

Writing Reducers.

For a reducer to accomplish its functionality the following takes place:

  • Look at the action type and determine the response to make.
  • Changing the state immutably by making copies of it and only modifying those copies.

Conditional logic might be the most basic way to use in a reducer. However, the switch statement is quite straightforward while handling multiple action types of a specific field. Modifying the state immutably by hand on the other hand is quite hard and easy to make mistakes.

Let’s then take a look at createReducer and explore its amazing features.

First of all, using createReducer solves the immutable issue by use of the Immer library which lets you write mutable code but at the same time converts it into an immutable code. Therefore the pain of remembering or looking up immutable methods like Concat() can be replaced with push(). But it all depends on user preference. Furthermore, spreading the state objects like:

…state.user in order to make copies of it so that we can update it is also another problem solved.

So what does a createReducer look like?

const todosReducer = createReducer([], (builder) => {
builder
.addCase('ADD_TODO', (state, action) => {
state.push(action.payload)
})
.addCase('TOGGLE_TODO', (state, action) => {
const todo = state[action.payload.index]
todo.completed = !todo.completed
})
.addCase('REMOVE_TODO', (state, action) => {
return state.filter((todo, i) => i !== action.payload.index)
})
})

Note

  • The createReducer is convertible to a switch statement. Each case—action type for example ‘TOGGLE_TODO’in a switch statement becomes a key of the object passed to the createReducer.

Writing Action Creators.

In the normal redux convention, action creators encapsulate the process of creating an action object which includes a type field and a payload field which is normally a parameter of the action creator function. This is what it may look like:

function addTodo(text) {
return {
type: 'ADD_TODO',
payload: { text },
}
}

However, with redux-toolkit, we can do this by the use of the createAction function.

const addTodo = createAction('ADD_TODO')
addTodo({ text: 'Buy milk' })

As you can see above, when providing the action type in createAction, this automatically converts addTodo into an action creator with its argument being the payload when it's called.

While creating reducers action creators can be used as action types. In redux you will find yourself defining an action creator and an action type separately like so:

// action type
const ADD_TODO = 'ADD_TODO'
// action creator
function addTodo(text) {
return {
type: 'ADD_TODO',
payload: { text },
}
}

However, with the createReducer function, we can leverage the use of the createAction function which will also act as our action type:

const actionCreator = createAction('SOME_ACTION_TYPE')// doing:
// console.log(actionCreator.toString())
// gives 'SOME_ACTION_TYPE'
const reducer = createReducer({}, (builder) => {
// here, we have actionCreator.toString() being called automatically on our addCase key meaning it will be a action type as expected.
builder.addCase(actionCreator, (state, action) => {})
builder.addCase(actionCreator, (state, action) => {})
})

Note

createAction can only be similar to an action type if used inside a createReducer function. If you want to get the action type by hand you can either use:

const actionCreator = createAction('SOME_ACTION_TYPE')
actionCreator.toString()
// oractionCreator.type

Simplifying Actions and Reducers using createSlice.

Now with createSlice things get a little bit easier and faster. It will auto-generate the action types and action creators for you, based on the names of the reducer functions you provide.

From the code below, createPost, updatePost and deletePost are each referred to as a “case reducer” that generates an action creator that uses the name of the reducer as the action type itself. So createPost reducer becomes the action type “post/createPost” while createPost() action creator will return an action with that type. Each “case reducer” or reducer function takes in the state and action parameters that then updates our state immutably.

In short, we are having all the redux flow happening in one place at the same time, under one function. Isn’t that cool?

Take a look at the code below:

const postsSlice = createSlice({
name: 'posts',
initialState: [],
reducers: {
createPost(state, action) {},
updatePost(state, action) {},
deletePost(state, action) {},
},
})

So how do we extract and export the actions from the slice?

Object Destructuring! You guessed that right.

From the above code this is how we will do it:

// Extract the action creators object and the reducer
const { actions, reducer } = postsSlice
// Extract and export each action creator by name
export const { createPost, updatePost, deletePost } = actions
// Export the reducer, either as a default or named export
export default reducer

As you can see from using createReducer to using createSlice the code becomes incredibly shorter and easier to read.

That’s it for now. Hope you get to like redux-toolkit as much as I do.

Thanks for reading!

--

--

Rose Sumba

Full Stack Developer(Ruby on Rails and React and Redux)