Using Redux Toolkit
Tags: redux, reduxtoolkit, example, tutorial
Published: 2020-06-30 07:45:01

[Redux Toolkit](https://redux-toolkit.js.org/) saves us a from a ton of boilerplate that you generally associate with Redux, making it easier to adopt and implement Redux in our app. It comes preloaded with all the tools which we usually need for building a Redux app. Additionally, we can also modify the given configurations to suit our needs.

# Main differences
There is a 3 part [tutorial](https://redux-toolkit.js.org/tutorials/basic-tutorial) in the Redux Toolkit docs to help you transform your boilerplate code into a sleek one. But I have listed the major differences below with code comparison:

### Installation
`@reduxjs/toolkit` comes preloaded with the redux dependencies as well as some essential middlewares.
* Redux Toolkit
```
yarn add @reduxjs/toolkit
```
* Redux
```
yarn add redux 
yarn add react-redux 
yarn add redux-immutable-state-invariant 
yarn add redux-thunk 
yarn add redux-devtools-extension
```

### Creating the store
In case of Redux Toolkit, [configureStore](https://redux-toolkit.js.org/usage/usage-guide#store-setup) calls `combineReducers` internally to create the rootReducer, so that all you have to do is pass an object and not worry about creating it manually. It also configures a few essential middlewares internally to help you debug and write clean and error-free code. These configurations are fully customisable, should you need it. Check the [official docs](https://redux-toolkit.js.org/usage/usage-guide) for more info.
* Redux Toolkit
```js
import { configureStore } from '@reduxjs/toolkit'
import filter from '...'
import movie from '...'

export default configureStore({
  reducer: {
    filter,
    movie,
  },
})
```
* Redux
```js
import { combineReducers, applyMiddleware, createStore } from "redux"
import reduxImmutableStateInvariant from 'redux-immutable-state-invariant'
import thunk from 'redux-thunk'
import { composeWithDevTools } from 'redux-devtools-extension/logOnlyInProduction'
import filter from '...'
import movie from '...'

// Custom middlewares based on redux-immutable-state-invariant
const immutableStateInvariant = // deeply compares state values for mutations. 
// It can detect mutations in reducers during a dispatch, and also mutations that 
// occur between dispatches (such as in a component or a selector). When a 
// mutation is detected, it will throw an error and indicate the key path for 
// where the mutated value was detected in the state tree.
const serializableStateInvariant = // a custom middleware created specifically 
// for use in Redux Toolkit. Similar in concept to immutable-state-invariant, 
// but deeply checks your state tree and your actions for non-serializable values 
// such as functions, Promises, Symbols, and other non-plain-JS-data values. 
// When a non-serializable value is detected, a console error will be printed 
// with the key path for where the non-serializable value was detected.
const middleware = process.env.NODE_ENV !== 'production' ?
  [thunk, immutableStateInvariant, serializableStateInvariant] :
  [thunk];
const rootReducer = combineReducers({ 
  filter, 
  movie, 
})
export default createStore(rootReducer, composeWithDevTools(
  applyMiddleware(...middleware)
))
```

### Creating reducers and sync actions (aka, slices)
Redux Toolkit introduces a new concept called [slices](https://redux-toolkit.js.org/usage/usage-guide#creating-slices-of-state), which is essentially a consolidated object containing the reducer and all the synchronous actions. No more definitions of actions and action types. Additionally, the state is mutable now courtesy of the included middlewares.
* Redux Toolkit
```js
import { createSlice } from '@reduxjs/toolkit'

const sliceName = 'movie'

const movieSlice = createSlice({
  name: sliceName,
  initialState: {
    entities: [],
    totalEntities: 0,
    error: '',
    loading: false,
  },
  reducers: {
    resetMovies: (state) => {
      state.entities = []
      state.totalEntities = 0
      state.error = ''
      state.loading = false
    },
  },
})

export const { resetMovies } = movieSlice.actions

export default movieSlice.reducer
```
* Redux
```js
const initialState = {
  entities: [],
  totalEntities: 0,
  error: '',
  loading: false,
}

const RESET_MOVIES = 'RESET_MOVIES'

export const resetMovies = () => ({
  type: RESET_MOVIES
})

export default function movie(state = initialState, action) {
  switch (action.type) {
    case RESET_MOVIES:
      return {
        entities: [],
        totalEntities: 0,
        error: '',
        loading: false,
      }
    default:
      return state
  }
}
```

### Creating async actions (aka, thunks)
Redux Toolkit also comes with the [createAsyncThunk](https://redux-toolkit.js.org/usage/usage-guide#async-requests-with-createasyncthunk) function. It gives us 3 implied sync actions for every thunk to be processed in our reducer, namely `<thunkStringName>.pending`, `<thunkStringName>.fulfilled` and `<thunkStringName>.rejected`. Thus, you don't have to manually define actions for these 3 states.
* Redux Toolkit
```js
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'

const sliceName = 'movie'

export const fetchMovies = createAsyncThunk(
  `${sliceName}/fetchMovies`,
  (_, { getState }) => {
    const { searchTerm, page, type } = getState().filter
    return movieAPI.fetchBySearch(searchTerm, page, type)
  }
)

const movieSlice = createSlice({
  ...
  extraReducers: {
    [fetchMovies.pending]: (state) => {
      state.loading = true
    },
    [fetchMovies.fulfilled]: (state, action) => {
      state.entities = action.payload.Search
      state.totalEntities = action.payload.totalResults
      state.error = ''
      state.loading = false
    },
    [fetchMovies.rejected]: (state, action) => {
      state.entities = []
      state.totalEntities = 0
      state.error = action.error.message
      state.loading = false
    },
  },
})
```
* Redux
```js
...

const FETCH_MOVIES_PENDING = 'FETCH_MOVIES_PENDING'
const FETCH_MOVIES_FULFILLED = 'FETCH_MOVIES_FULFILLED'
const FETCH_MOVIES_REJECTED = 'FETCH_MOVIES_REJECTED'

...

export const fetchMoviesPending = () => ({
  type: FETCH_MOVIES_PENDING
})
export const fetchMoviesFulfilled = (result) => ({
  type: FETCH_MOVIES_FULFILLED,
  payload: result
})
export const fetchMoviesRejected = (error) => ({
  type: FETCH_MOVIES_REJECTED,
  payload: error
})

export function fetchMovies() {
  return async function (dispatch, getState) {
    dispatch(fetchMoviesPending())
    const { searchTerm, page, type } = getState().filter
    try {
      const result = await movieAPI.fetchBySearch(searchTerm, page, type)
      dispatch(fetchMoviesFulfilled(result))
    } catch (error) {
      dispatch(fetchMoviesRejected(error))
    }
  }
}

export default function movie(...) {
  switch (action.type) {
    ...
    case FETCH_MOVIES_PENDING:
      return {
        ...state,
        loading: true,
      }
    case FETCH_MOVIES_FULFILLED:
      return {
        entities: action.payload.Search,
        totalEntities: action.payload.totalResults,
        error: '',
        loading: false,
      }
    case FETCH_MOVIES_REJECTED:
      return {
        entities: [],
        totalEntities: 0,
        error: action.error.message,
        loading: false,
      }
    ...
  }
}
```

# Usage
Once the store and the slices have been created, you can setup Redux in your app the same way as you've always have.
* index.jsx
```jsx
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import store from './store'
import { Provider } from 'react-redux'

ReactDOM.render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>,
  document.getElementById('root')
)
```
* Movies/index.jsx
```jsx
import React, { useEffect } from 'react'
import Movies from './presenter'
import { useSelector, shallowEqual, useDispatch } from 'react-redux'
import { search } from '../../services/filter/slice'

export default () => {
  const { entities, totalEntities, error, loading } = useSelector(
    (state) => state.movie,
    shallowEqual
  )
  const searchTerm = useSelector((state) => state.filter.searchTerm)
  const dispatch = useDispatch()

  useEffect(() => {
    dispatch(search(searchTerm))
  }, [dispatch, searchTerm])

  return (
    <Movies
      entities={entities}
      totalEntities={totalEntities}
      error={error}
      loading={loading}
    />
  )
}
```

# Repository
{% github batbrain9392/redux-tutorial %}