Redux is an open-source JavaScript library for managing application state. ‌‌Redux Toolkit is an opinionated, batteries-included tool set for Redux development.

Redux Toolkit exposes a set of tools that help us implement Redux in a simpler (albeit opinionated) way.

Implementing Redux in a project is done by creating Actions and Reducers. Actions describe what changes we want to happen to the store and the Reducers execute these changes and then the Redux store notifies our components that the store values have changed and they re-render with the updated values.

Actions are simply a JS object containing the type of action we want to perform and a payload which is the data needed for that action.

for example here, our action is to add a todo, and we provide the data in the payload with the information of that todo:

{
  type: 'ADD_TODO',
  payload: {
    name: 'Buy Groceries',
  },
}

It is helpful to avoid typos and conflicts to create constants containing our action types and then using those instead like so:

const ADD_TODO = 'ADD_TODO'

const action = {
  type: ADD_TODO,
  payload: {
    name: 'Buy Groceries',
  },
}

and to further make it easier to send actions, we can create a function that returns the complete object for us:

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

Reducers are function that takes the previous state, the action we want to perform, and returns the new state after performing that action, a reducer can perform more than one action so we use a switch-case statement to know which operation to perform after knowing which action was sent. we can also pass an initial state to our reducers.

for example to add a todo:

const initialState = [
  { name: 'Wash the car'. completed: false }, 
  { name: 'Wash the clothes', completed: false }
]

function todosReducer(state = initialState, action) {
  switch (action.type) {
    case ADD_TODO:
      return [...state.todos, {...action.payload, completed: false}]
    default:
      return state
  }
}

In Redux the state must not change, we must always return a new state, that is why we didn't do state.push(action.payload) but instead created a new array, used to spread operator to copy all the previous todos in the new array and add our new todo, then returned that new array, which is our new state.

After that, we can create more reducers and combines them using combineReducers function from redux into one root reducer, and finally pass it on to the store:

const rootReducer = combineReducers({
  todosReducer
})

const store = createStore(reducer)

then we can get the store current state, subscribe to the store, and dispatch actions as we like:

function onStateChange() {
  console.log(store.getState())
}
store.subscribe(onStateChange)

const newTodo = {name: 'Buy Groceries'}
store.dispatch({ type: 'ADD_TODO', payload: newTodo })
// or use the previously created function
store.dispatch(addTodo('Buy Groceries'))

Now, there is a lot of extra boilerplate that goes into Redux, and Redux Toolkit takes away a lot of that boilerplate off us.

Amongst other functions, it provides us with a function called createSlice which handles the logic of creating action types, action creators, reducers, a switch-case/if-else, returning state on default case, returning a new state (give us a copied state allowing us to modify it directly and automatically returning it as new). It also provides a configureStore function which combine reducers, enable Redux DevTools, and add redux-thunk (for async actions):

So we can rewrite the previous code with Redux Toolkit like so:

const todosSlice = createSlice({
  name: 'todos',
  initialState: [
    { name: 'Wash the car', completed: false },
    { name: 'Wash the clothes', completed: false },
  ],
  reducers: {
    addTodo: (state, action) {
      state.push(action.payload)
    }
  },
})

const store = configureStore({
  reducer: {
    todos: todosSlice
  }
})

So let's review all the previous code we did without Redux Toolkit and create the rest of actions while we at it:

import { createStore, combineReducers } from 'redux'

const ADD_TODO = 'ADD_TODO'
const REMOVE_TODO = 'REMOVE_TODO'
const TOGGLE_TODO = 'TOGGLE_TODO'

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

function removeTodo(index) {
  return {
    type: 'REMOVE_TODO',
    payload: { index }
  }
}

function toggleTodo(index) {
  return {
    type: 'TOGGLE_TODO',
    payload: { index }
  }
}

const initialState = [
  { name: 'Wash the car', completed: false },
  { name: 'Wash the clothes', completed: false },
]

function todosReducer(state = initialState, action) {
  switch (action.type) {
    case ADD_TODO:
      return [...state, action.payload]
    case REMOVE_TODO:
      return state.filter((todo, index) => {
        if (index === action.payload.index) {
          return false
        }
        return true
      })
    case TOGGLE_TODO:
      return state.map((todo, index) => {
        if (index === action.payload.index) {
          return {
            ...todo,
            completed: !todo.completed,
          }
        }
        return todo
      })
    default:
      return state
  }
}

const rootReducer = combineReducers({
  todosReducer
})

const store = createStore(reducer)

export default store
export {
    addTodo,
    removeTodo,
    toggleTodo
}

// To test that the store is working
function onStateChange() {
  console.log(store.getState())
}
store.subscribe(onStateChange)

store.dispatch(addTodo('Buy Groceries'))
store.js (78 Lines)

And now let's rewrite it using Redux Toolkit:

import { createSlice, configureStore } from '@reduxjs/toolkit'

const todosSlice = createSlice({
  name: 'todos',
  initialState: [
    { name: 'Wash the car', completed: false },
    { name: 'Wash the clothes', completed: false },
  ],
  reducers: {
    addTodo(state, action) {
      state.push(action.payload)
    },
    removeTodo(state, action) {
    	state.splice(action.payload.index, 1)
    },
    toggleTodo(state, action) {
    	state[index].completed = !state[index].completed
    },
  },
})

const store = configureStore({
  reducer: {
    todos: todosSlice
  }
})

export default store
export const { addTodo, removeTodo, toggleTodo } = todosSlice.actions

// To test that the store is working
function onStateChange() {
  console.log(store.getState())
}
store.subscribe(onStateChange)
store.dispatch(todosSlice.actions.addTodo('Buy Groceries'))
store.js (36 Lines)

Think of the createSlice slices as store modules and you can create as many modules (slices) as you need and then add them to the object you pass to configureStore


You can now proceed to integrate your Redux store with React using React Redux by wrapping your app with a store Provider:

import React from 'react'
import ReactDOM from 'react-dom'

import { Provider } from 'react-redux'
import store from './store'

import App from './App'

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

And connecting your components with connect:

import { connect } from 'react-redux'
import { addTodo, removeTodo, toggleTodo } from './store'

// const Todos = ...

const mapStateToProps = (state /*, ownProps*/) => ({
  todos: state.todos,
})

// "object shorthand" form of mapDispatch with action creators
const mapDispatchToProps = { addTodo, removeTodo, toggleTodo }

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(Todos)
Todos.js

Don't forget to npm install ... (redux is included with @reduxjs/toolkit):

npm install @reduxjs/toolkit react-redux