DEV Community

loading...

Redux Toolkit Basic Intro

bdesigned profile image Brittney Postma Originally published at console-logs.netlify.app Updated on ・5 min read

Hi, I'm Brittney and I'm an instructor over at ZTM Academy and the owner, designer, and developer at bDesigned. You can find more dev notes by me at Console Logs.


TLDR: Redux Toolkit is the new official way to add Redux to your application. It allows you to set up and add items to your store with less code. See the code or the demo.

Redux Toolkit Logo


Table of Contents

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

     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
Enter fullscreen mode Exit fullscreen mode

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
})
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 ❤️!

Discussion (11)

pic
Editor guide
Collapse
miku86 profile image
miku86

I love RTK, it reduces a lot of boilerplate that makes it sometimes hard to understand the (simple) concepts behind Redux. @markerikson is doing a great job!

Collapse
markerikson profile image
Mark Erikson

Thanks!

Since we're on that topic: I'm currently working on a new "Quick Start" tutorial for the Redux core docs, which is meant to be a higher-level "here's what to do, the right way" tutorial for people who have never used Redux before. It's going to teach RTK and the React-Redux hooks as the default approach for using Redux.

It's not done yet, but I'd appreciate any feedback folks might have on the WIP content I've got so far (do the explanations make sense, questions that aren't answered after reading, things you want to see covered, etc). You can view the preview here:

deploy-preview-3740--redux-docs.ne...

Hoping to get Part 3 of that tutorial put together today or tomorrow.

Collapse
bdesigned profile image
Brittney Postma Author

💯 He is doing awesome and is helpful and active in the community!

Collapse
markerikson profile image
Mark Erikson

Nice post, and glad to hear that RTK is working well for you!

A few quick notes on the code samples:

First, you may actually have a small issue with one of those reducers:

reducer: ( state, { payload }) => state.push(payload),

Immer wants you to either return a new value, or mutate its state, but not both. This is mutating with push(), which is fine, but also returning the result of push() because of the implicit arrow return. Immer will probably yell at you for that. You can fix it by throwing curlies around the body, or putting void in front of the body.

The findIndex() calls look like they're missing a = assignment. Personally, I'd call find() instead of findIndex():

const todo = state.find(todo => todo.id === payload.id)
if(todo) {
  todo.completed = !todo.completed;
}

Finally, while it's totally fine to use a findIndex() + a splice() to remove an item, I find it's usually shorter to do it with a .filter() instead and just return the new array same as you would with more traditional hand-written reducers. (And that's one of the nice things about Immer and RTK - you can mix and match update styles as appropriate!)

Also, the link to the "Why RTK uses thunks" post is going through an odd Youtube redirect first. The actual link is blog.isquaredsoftware.com/2020/02/... - would be good to update to point to that directly.

Collapse
bdesigned profile image
Brittney Postma Author

Okay, I will update it now. I guess I didn't proofread very well. I believe I made all the changes except the filter instead of slice. Haven't had my coffee yet and my brain isn't working 😂

Thank you for the input and let me know if anyone else sees more typos 😉

All updated now and made sure it was functional. Helps to code with spellchecker and linter 😂

Collapse
enzo profile image
Josema Enzo

Hi, nice post. But I miss the step of using it inside a component.

Collapse
rgnt profile image
Riku-Juhani Niemelä

Hi, for using it inside the component is same as with plain old redux - using React-Redux's useSelector and useDispatch, or HOCs.

Collapse
enzo profile image
Josema Enzo

If you are not familiar with Redux, you don't know how to use useSelector or useDispatch. So it would be great to see it in this summary.

Thread Thread
bdesigned profile image
Brittney Postma Author

I'll add a little piece onto the end after my coffee 😀

Thread Thread
bdesigned profile image
Brittney Postma Author

Okay, it is all there now and should be functional. It helps to actually code with a spellchecker and linter 😂

Thread Thread
enzo profile image
Josema Enzo

Thank you for taking the time of adding it :)