September 23, 2023

Redux Toolkit in React With Examples

In this tutorial we'll see an introduction of Redux Toolkit and how to use it with React application.

In the post Redux in React With Examples we saw how you can create store using Redux and react-redux library. Though that is not the suggested way to create redux store but I'll suggest to go through that post to get a better idea of how Redux works. With the old way you generally have to write most of the logic yourself so it gives you a better understanding of what is a store, reducer and action. But the suggested way now is to use configureStore() provided by @reduxjs/toolkit library so let's try to understand how that works.

Using Redux toolkit

Though Redux library helps in managing global state and making state management more efficient but you may still experience the following problems.

  1. As the complexity of your project increases complexity of Redux also increases.
  2. Writing reducers and actions become more complex and their number also increases with bigger applications.
  3. State object may become big which results in keeping state immutable quite a complex task.

In order to address these issues and making it more convenient to work with Redux, the same team developed Redux toolkit. With Redux toolkit you can say that some of the steps that were done manually with Redux are now automatically derived. It also makes it more convenient to modify the stored state without worrying about its immutability as Redux toolkit uses immer library which takes care of state immutability behind the scene rather than you ensuring it.

The Redux Toolkit package is intended to be the standard way to write Redux logic.

Using @reduxjs/toolkit with react

Let's try to create the same counter application using Redux toolkit which was done in this post using Redux.

1. Install packages

If you already have a React application just navigate to the app base folder and run the following command to install Redux toolkit

npm install @reduxjs/toolkit react-redux

react-redux is the React binding library for Redux.

2. Create store

src\store\tkStore.js

import { configureStore } from "@reduxjs/toolkit";


const store = configureStore({
    reducer: { } // that's where we'll add reducer
})

export default store;

With Redux toolkit you start using configureStore() to create store rather than createStore() used with Redux.

configureStore() is an abstraction over the standard Redux createStore function that adds good defaults to the store setup for a better development experience.

configureStore accepts a single configuration object parameter, with many options. Options of interest to us initially are-

  • reducer- To accept reucer functions.
  • devTools- To indicate whether configureStore should automatically enable support for the Redux DevTools browser extension. Defaults to true.

For more details you can see the API- https://redux-toolkit.js.org/api/configureStore

3. Provide store to React

React Redux includes a <Provider /> component, which makes the Redux store available to the React app. We'll wrap <App /> component with the Provider which guarantees that the store provided with this Provider is available to App component and all its child components. That change can be done in index.js file.

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

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

4. Create a state slice

You can import the createSlice API from Redux Toolkit. This is the most important part as with slice you can write reducer function and actions with in the slice.

src\slice\counterSlice.js

import { createSlice } from "@reduxjs/toolkit";

const counterSlice = createSlice({
    name: 'counter',
    initialState: {value: 0},
    reducers: {
        increment: (state) => {
            state.value++;
        }, 
        decrement: (state) => {
            state.value--;
        },
        increaseByValue: (state, action) => {
            state.value += action.payload;
        }
    
    }
});

export const counterActions = counterSlice.actions;
export default counterSlice.reducer;

Some important points to note here are-

  • Creating a slice requires a name to identify the slice, which is given as ‘counter’ here.
    name: 'counter',
    
  • Slice also needs initial state value.
    initialState: {value: 0},
    
  • You can have one or more reducer functions to define how the state can be updated. You can think of reducer as a map where key is the action name and the value is the logic to update state. For example, with this piece of code.
    increment: (state) => {
                state.value++;
            }, 
    
    
    Redux generates an action named 'increment'. With the use of createSlice Redux toolkit makes it easier to define reducers and actions, you also don't need condition statements (if or switch) to determine the correct action type.
  • Another very important thing is how state is changed. It looks like, now we are mutating state rather than creating a new state altogether.
     state.value++;
    
    This is permitted with Redux toolkit because it uses immer as explained already.
  • Export the reducers and actions so that they can be used where needed.
    export const counterActions = counterSlice.actions;
    export default counterSlice.reducer;
    
    You can also use object destructuring to export the generated action creators separately.
    export const { increment, decrement, increaseByValue} = counterSlice.actions;
    

5. Add reducer to the store

Now we have defined and exported the reducer so let's revisit the store to add reducer there.

import { configureStore } from "@reduxjs/toolkit";
import counterReducer from "../slice/counterSlice";
const store = configureStore({
    reducer: {
        counterReducer: counterReducer
    }
})

You can also add more than one slice reducers (an object of slice reducers)

configureStore({
    reducer: {
        counterReducer: counterReducer, 
	    userReducer: userReducer
    }
})

configureStore will automatically create the root reducer by passing this object to the Redux combineReducers utility.

6. Using actions in components

We'll use two hooks useSelector() and useDispatch() provided by react-redux. useSelector() hooks allows you to extract data from the Redux store state. useDispatch() hook is used to dispatch actions.

src\components\CounterWithRedux.js

import { useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { counterActions } from "../slice/counterSlice";

const CounterWithRedux = () => {
    const counter = useSelector((state) => state.counterReducer.value);
    const dispatch = useDispatch();
    const [incrementByValue, setIncrementByValue] = useState(0);
    const incrementHandler = () => {
        dispatch(counterActions.increment());
    }
    const decrementHandler =  () => {
        dispatch(counterActions.decrement());
    }
    const incrementByValueHandler = () => {
        dispatch(counterActions.increaseByValue(Number(incrementByValue)));
    }
    return (
        <div>
            <h2>Counter</h2>
            <div>
                <button className="btn btn-primary mx-2" onClick={incrementHandler}>+</button>            
                <span>{counter}</span>            
                <button className="btn btn-primary mx-2" onClick={decrementHandler}>-</button>
                <div className="mt-2">
                    <input value={incrementByValue} onChange={e => setIncrementByValue(e.target.value)}></input>
                    <button className="btn btn-primary mx-2" onClick={incrementByValueHandler}>Add Amount</button>
                </div>
            </div>
        </div>
    );
}

export default CounterWithRedux;

Some important points to note here-

  • We have used useSelector() to read data from store.
    const counter = useSelector((state) => state.counterReducer.value);
    

    What we are doing here is to go to the state produced by counterReducer.

    Remember that we have given 'counterReducer' identifier to our reducer which is also named counterReducer.

    import counterReducer from "../slice/counterSlice";
    const store = configureStore({
        reducer: {
            counterReducer: counterReducer
        }
    })
    

    From that state we are extracting data from 'value' property. Again, that's how we defined the state.

    initialState: {value: 0}
    
  • We can import generated action creators which is imported in CounterSlice.js file.
    import { counterActions } from "../slice/counterSlice";
    

    Now we can dispatch the required action by accessing it through counterActions.

    dispatch(counterActions.increment());
    dispatch(counterActions.decrement());
    
  • Note that Bootstrap is used here for styling.
Redux toolkit with React example

Source: https://redux-toolkit.js.org/tutorials/quick-start

That's all for the topic Redux Toolkit in React With Examples. If something is missing or you have something to share about the topic please write a comment.


You may also like

No comments:

Post a Comment