go back

Redux Toolkit

Table of Contents

This is also available on Dev.to. Please, go give it some 💖 Redux Toolkit Basic Intro!

Redux Toolkit Logo

What is Redux Toolkit?

     Redux Toolkit is the new official way to incorporate Redux into your project. It tries to solve some of the common concerns developers expressed in using the original Redux package. Such as too much setup, too complicated, and needed too many addon packages to function. With toolkit, there is less configuration and a lot more work is done under the hood and middlewares have been integrated in for async thunks. While the original Redux package was very unopinionated and allowed you to choose which packages you wanted to use with it, the new Redux Toolkit is opinionated because it comes with those packages out of the box. You can think of Redux Toolkit as the Create React App for Redux as it comes with things that will help get you started faster. Here's a list of the new APIs from Redux Toolkit Docs:

  • ♦ configureStore(): wraps createStore to provide simplified configuration options and good defaults. It can automatically combine your slice reducers, adds whatever Redux middleware you supply, includes redux-thunk by default, and enables use of the Redux DevTools Extension.
  • ♦ createReducer(): that 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.
  • ♦ 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.
  • ♦ createSlice(): accepts an object of reducer functions, a slice name, and an initial state value, and automatically generates a slice reducer with corresponding action creators and action types.
  • ♦ createAsyncThunk: accepts an action type string and a function that returns a promise, and generates a thunk that dispatches pending/fulfilled/rejected action types based on that promise
  • ♦ createEntityAdapter: generates a set of reusable reducers and selectors to manage normalized data in the store.
  • ♦ createSelector- utility from the Reselect library, re-exported for ease of use.

Why Redux Toolkit?

     As I said above, using Redux Toolkit will greatly decrease the configuration and setup of the Redux store. This will get you ready to code faster and simplify adding new items to your store. While the bundle size is going to be larger than the original Redux package, the RTK team is constantly working on better tree-shaking techniques to decrease its size. As Redux Toolkit installs the individual packages, you always have the option to remove pieces you aren't using as well. It also chooses to use redux-thunk over redux-saga and you can swap those out if you wish. Here is more information on Why RTK uses Redux Thunk over Redux Saga, if you want to know more.

Using Redux Toolkit

To start a new project with Redux Toolkit:

npx create-react-app my-app-name --template redux

If it is a React project, you will also need react-redux. To add Redux Toolkit to an existing app:

// NPM
npm i @reduxjs/toolkit

// With React
npm i @reduxjs/toolkit react-redux

// Yarn
yarn add @reduxjs/toolkit

// With React
yarn add @reduxjs/toolkit react-redux

     Coming from the original Redux package, you may think createAction() and createReducer() are going to be your first files to setup. Even though you can still set it up that way, most of the time, all you are going to need is the createSlice() function. It will accept a slice name string, an initial state and an object of reducer functions as parameters and will automatically generate the action creators and types that correspond to the reducers and state. It essentially combines 3 files into 1. Create a redux folder and a todosSlice.js file. Let's take a look at a slice for setting up a todo.

import { createSlice } from '@reduxjs/toolkit'

let nextTodoId = 0

const todosSlice = createSlice({
  // slice name
  name: 'todos',
  // initial state
  initialState: [
    {
      id: 1,
      desc: 'name slice',
      isComplete: true
    },
    {
      id: 2,
      desc: 'set initial state',
      isComplete: true
    },
    {
      id: 3,
      desc: 'create reducer',
      isComplete: false
    }
  ],
  // object of reducer functions
  reducers: {
    // action
    create: {
      reducer(state, { payload }) {
        const { id, desc } = payload
        state.push({ id, desc, isComplete: false })
      },
      prepare(desc) {
        return {
          payload: {
            desc,
            id: nextTodoId++
          }
        }
      }
    },
    // action
    toggle: (state, { payload }) => {
      // reducer
      const todo = state.find(todo => todo.id === payload.id)
      if (todo) {
        todo.isComplete = !todo.isComplete
      }
    },
    // action
    remove: (state, { payload }) => {
      // reducer
      const idx = state.findIndex(todo => todo.id === payload.id)
      if (idx !== -1) {
        state.splice(idx, 1)
      }
    }
  }
})

// destructuring actions and reducer from the slice
const { actions, reducer } = todosSlice

// destructuring actions off slice and exporting
export const { create, toggle, remove } = actions

// export reducer
export default reducer

Next, we need to create a rootReducer.js file to combine our reducers for the store.

import { combineReducers } from '@reduxjs/toolkit'
// import the reducer as todosReducer
import todosReducer from './todosSlice'

export default combineReducers({
  // other slices would be added here
  todos: todosReducer
})

Finally, we just need to configure the store. You can do this by creating a store.js file or just include it in the index.js.

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

const store = configureStore({
  reducer: rootReducer
})

export default store

Now in index.js, we need to wrap the Provider around our main component.

import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import * as serviceWorker from './serviceWorker'
import store from './redux/store'
import { Provider } from 'react-redux'

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)

serviceWorker.register()

Now, we can use the react-redux hooks to pull in our Redux store to our app.

import React, { useState } from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { create, toggle, remove } from '../redux/todosSlice'
import './App.css'

const App = () => {
  const dispatch = useDispatch()
  // get todos from state
  const todos = useSelector(state => state.todos)
  const completed = useSelector(state => state.todos.isComplete)
  const [todoInput, setTodoInput] = useState('')

  const handleInputChange = e => {
    setTodoInput(e.target.value)
  }

  const handleNewTodo = e => {
    e.preventDefault()
    // if no input, just return
    if (!todoInput.length) return
    // dispatch will send our create action
    dispatch(create(todoInput))
    // reset input
    setTodoInput('')
  }

  const handleToggle = id => {
    // send toggle action updated state
    dispatch(
      toggle({
        id,
        isComplete: !completed
      })
    )
  }

  return (
    <div className='App'>
      <div className='App__header'>
        <h1>Todo: RTK Edition</h1>
        <form onSubmit={handleNewTodo}>
          <label htmlFor='new-todo' style={{ display: 'none' }}>
            New Todo:
          </label>
          <input
            onChange={handleInputChange}
            id='new-todo'
            value={todoInput}
            placeholder='todo...'
          />
          <button type='submit'>Create</button>
        </form>
      </div>
      <div className='App__body'>
        <ul className='App__list'>
          {todos.map(todo => (
            <li
              className={`${todo.isComplete ? 'done' : ''}`}
              key={todo.id}
              onClick={() => handleToggle(todo.id)}>
              {todo.desc}
            </li>
          ))}
        </ul>
      </div>
    </div>
  )
}

export default App

That's it! Redux Toolkit is now set up and connected to our application. This is a basic tutorial, next time we will dive deeper into RTK! Thanks for the 💖!