Redux Toolkit & its usage
Published: 2022-02-25 08:54:24

As a result of **Redux Toolkit**, you don't have to worry about a lot of boilerplate when using Redux, making your app easier to adopt and implement Redux. The toolkit comes preloaded with the tools you might need when building a Redux app. Moreover, we can also customize the given configurations to suit our specific needs.
 
**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
```

**Store Creation**
For Redux Toolkit, configureStore internally calls combineReducers to create the rootReducer, so that you don't have to worry about creating it manually. As well as configuring a few essential middlewares internally, it also helps you write clean and error-free code. These settings can be modified as needed according to the official documentation.

- Redux Toolkit

```
import { configureStore } from '@reduxjs/toolkit'
import filter from '...'
import movie from '...'

export default configureStore({
  reducer: {
    filter,
    movie,
  },
})
```

- Redux

```
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, which is essentially a consolidated object containing the reducer and all the synchronous actions. No more action types or types of actions, and the state can now be mutated.

- Redux Toolkit

```
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

```
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)**
Additionally, Redux Toolkit includes the 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. Therefore, we do not need to define actions for each state. We get 3 implied sync actions for each thunk to be processed in our reducers. These are `<thunkStringName>.pending` , `<thunkStringName>.fulfilled` and `<thunkStringName>.rejected`.

- Redux Toolkit

```
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

```
...

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 In React APP**
Once the store and the slices have been created, you can setup Redux in your app the same way as you've always have.

```
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

```
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}
    />
  )
}

```

**Sources: **
_Codes are taken from this github. _
**Github **  https://github.com/batbrain9392/redux-tutorial