Using Redux Toolkit with React

Using the Redux Toolkit package gives a standard way to write Redux logic in our React application. The Redux toolkit reduces the complicated redux store configuration. It handles the most common use cases, and it also includes some useful utilities that simplify our application code.

A store holds the whole state tree of our application. Redux is used to manage the state of data and access them at different components level in the application. Redux Tool Kit provides the easiest way to configure the global store and create actions and reducers more streamlined manner by abstracting the Redux API.

In this article, we are going to discuss APIs included in the Redux toolkit, and how to set up and use Redux Toolkit with React application.

APIs included in the Redux toolkit

The following API function is used by Redux Took Kit, which does not change the flow of Redux but streamlines them in a more manageable manner.

  1. configureStore:
    The configureStore Creates a Redux store instance like the original createStore from Redux.  It can automatically combine your slice reducers, add whatever Redux middleware you supply, middleware to check for common mistakes like accidentally mutating the state, and includes redux-thunk by default.
     
  2. createAction:
    The 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.
     
  3. createReducer:
    The createReducer accepts an initial state value and supplies a lookup table of action types to case reducer functions and creates a reducer that handles all action types. It internally uses the immer library to let you write simpler immutable updates with normal mutative code.
     
  4. createSlice:
    The createSlice function accepts an initial state and a lookup table with reducer names and functions and automatically generates a slice reducer with corresponding action creators and action types.
    The createSlice takes an object as an argument with three option fields:
    name: a string that will be used as the prefix for generated action types
    initialState: the initial state of the reducer
    reducers: an object where the keys are strings, and the values are "case reducer" functions that will change the state

Installing Redux Toolkit

Redux Toolkit can be installed with the below command in our project root directory.

# NPM
npm install @reduxjs/toolkit react-redux

# Yarn
yarn add @reduxjs/toolkit react-redux

Create a Redux store using the redux toolkit

Let us see how we can use createSlice and configureStore to create a redux store. Please look at the below code to create a redux store.

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

const counterSlice = createSlice({
    name:'Counter',
    initialState : {
        count:0,
        showCount:false
    },
    reducers:{
        increment(state){
            state.count++;
        },
        decrement(state){
            state.count--;
        },
        increase(state, action){
            state.count = state.count + action.payload;
        }
    }
})

const store = configureStore({
    reducer:counterSlice.reducer
});

export const counterActions = counterSlice.actions;

export default store;

The redux toolkit enables us to make all the actions and reducers in a single place and doesn’t need to use a switch statement to identify the action as in the traditional redux implementation. We have imported createSlice from the redux toolkit as in the above code.  The createSlice is a higher-order function that accepts an initial state, an object full of reducer functions, and the slice name. It will generate action creators and action types that correspond to the given reducers and state. But Internally, it uses createAction and createReducer functions as in the traditional redux implementation.

We are changing the state directly from the reducer functions. Since the Redux toolkit uses Immer, we do not have to worry about mutating the state. Immer internally to let you write simpler immutable update logic using "mutating" syntax. This helps us to simplify most reducer implementations.

The createSlice will return an object that looks like:

{
    name : string,
    reducer : ReducerFunction,
    actions : Record<string, ActionCreator>,
    caseReducers: Record<string, CaseReducer>.
    getInitialState: () => State
}

The createSlice method helps us to create slices of a large redux store. For example, in an eCommerce application, we can create separate slices for product management and order management like productSlice and orderSlice.

The configureStore is responsible for creating the redux store. It accepts a single configuration object with a parameter as a reducer. We can also pass a middleware parameter with an optional array of Redux middleware functions. If we are using this option, it should contain all the middleware functions you want to be added to the store and it will automatically pass those to applyMiddleware. If we have not provided this option then, it will call getDefaultMiddleware and use the array of middleware functions it returns.

The return of configureStore is the store object which is exported from the redux.

Include the Redux Store to index.js parent

The next step is to include the redux store in our index.js. Please have a look at the below code.

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';

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

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  	<React.StrictMode>
    	<Provider store={store}>
      		<App />
    	</Provider>
  	</React.StrictMode>
);

In the index.js file, we have imported the Provider component from the ‘react-redux’ and imported the store from the redux store. The entire App component was wrapped with the Provider component and passed store as a props into it. So the state can be accessed anywhere in the App component and its child components.

Using State in the App component

The useSelector and useDispatch are react hooks imported from react-redux and they are alternatives to the existing connect() higher-order component. The equivalent of map state to props is useSelector. It takes a function as an argument and it returns the part of the state that you want. The equivalent of map dispatch to props is useDispatch. We can invoke the useDispatch function and store it to a const dispatch. Dispatch will work with the allActions imported from the actions folder. Please have look at the below example code.

import React from 'react';
import {useSelector, useDispatch} from 'react-redux'
import { counterActions } from './store/index';

export default function App() { 
	const dispatch = useDispatch();
	const count = useSelector((state)=>state.count);

	const incrementClickHandler = () => {
		dispatch(counterActions.increment());
	}

	const decrementClickHandler = () => {
		dispatch(counterActions.decrement());
	}

	const increaseClickHandler = () =>{
		dispatch(counterActions.increase(10));
	}

  	return (
		<>
			<button onClick={incrementClickHandler}>Increment</button>
			<button onClick={decrementClickHandler}>Decrement</button>
			<button onClick={increaseClickHandler}>Increase by 10</button>
			<p>Count : {count}</p>
		</>
	);
}

In the above code, we took the count state from the redux store with help of useSelecter hook like:

 const count = useSelector((state)=>state.count);

Also, we are calling the redux action with help of dispatch functions like:

dispatch(counterActions.increment());

Tools to debug redux

The Redux DevTools help us to debug the application's state changes. It also allows us to time-travel debugging and live editing and let you go back in time by “canceling” actions.

There are two variants of Redux dev tools are available:

  1.  Redux DevTools which can be installed as a package and integrated into our application. 
    https://github.com/reduxjs/redux-devtools/blob/main/docs/Walkthrough.md#manual-integration
  2. Redux DevTools chrome extension that implements the same developer tools for Redux in the browser. It adds an extra tab in the browser Developer tools(inspect). We download the chrome extension from Chrome Web Store.

RTK Query

RTK Query is an advanced data fetching and caching tool, designed to simplify common cases for loading data in a web application. RTK Query is built on top of the Redux Toolkit, and it uses APIs like createSlice and createAsyncThunk to implement its capabilities. It was included as an addon to the redux toolkit. It is not mandatory to use the RTK Query APIs when we use Redux Toolkit. But If we want to use of advantages and capabilities of RTK Query's data fetching and caching, we can utilize it.

RTK Query enables us to define the entire API definition in one place. It is much easier to track how requests, cache invalidation, and general app configuration behave when they're all in one central location using RTK Query.

RTK Query can also generate React hooks that can encapsulate the entire data fetching process, provide data and isFetching fields to components, and manage the lifetime of cached data as components mount and unmount. It also provides "cache entry lifecycle" options that enable use cases like streaming cache updates via WebSocket messages after fetching the initial data.

RTK Query primarily consists of two APIs:

  • createApi(): The core of RTK Query's functionality. The createApi function allows us to define a set of API endpoints that describe how to retrieve data from the endpoints, including the configuration of how to fetch and transform that data. In most cases, we want only use this once, with “one API slice per base URL”.
  • fetchBaseQuery(): A small wrapper around fetch that aims to simplify API requests.

For more depth knowledge on RTK Query, visit the Redux toolkit doc.

Conclusion

To set up a Redux Toolkit in the React Application:

  1. Install Redux Toolkit.
  2. Use createSlice and configureStore to create a redux store.
  3. Include the Redux Store to index.js and pass the store as a props into the provider component.
  4. Implement redux in React component using useSelector and useDispatch hooks from react-redux.

Use Redux DevTools to debug the redux and monitor application state changes.