In the world of building large applications, one of the biggest challenges, especially for teams of developers, is managing the state. While the Context API is useful, it has its limits, especially for applications with many features. To address this, several state management tools have been introduced, but Redux has emerged as a leading solution.
It is worth noting that Redux is not part of ReactJS, despite being frequently used together. While Redux solves many problems, it also introduces new challenges. Setting up a Redux store and writing reducers can be a cumbersome and error-prone process that involves a lot of boilerplate code and manual configuration. This is where the Redux Toolkit comes in - the creators of Redux recognized the need for a more opinionated approach to setting up Redux applications, resulting in the development of the Redux Toolkit, which can be thought of as Redux with batteries included.
The Redux Toolkit eliminates the need for additional libraries and configurations, speeding up the workflow significantly. With the Redux Toolkit, users can benefit from Redux without having to deal with manual setups.
This article will cover all the essential building blocks of the Redux Toolkit. Specifically, we will be developing a shopping cart application that allows users to add and remove items, displaying the total price of the items in their cart. We will explore many of the features of the Redux Toolkit, including Store, Slice, Reducers, and Action Creators.
What we will cover
Setting up the Project
Installing Redux Toolkit
Creating the Redux Store
Setup Provider
Creating the Cart Slice
Creating the UI Components
Rendering the App
Running the App
Conclusion
Step 1: Setting up the Project
We will start by creating a new React project using Create React App. Open up your terminal and run the following command:
npx create-react-app shopping-cart
Once the project is created, navigate to the project directory:
cd shopping-cart
Step 2: Installing Redux Toolkit
Next, we need to install Redux Toolkit. Run the following command in your terminal:
npm install @reduxjs/toolkit react-redux
This will install the latest version of Redux Toolkit and add it to our project dependencies. Redux can be used for any frontend framework and is not specific to react, so in order to connect our Redux with react this is where the react-redux
comes in.
Just a side note: When we install @reduxjs/toolkit
we actually installed a few libraries which are:
- redux (core library, state management)
- immer (which allow us to mutate the state)
- redux-thunk (which will handles the async actions)
- reselect (which will simplifies the reducer functions)
All these will make sense if you have worked with Redux, but if you haven't worked with Redux before don't worry I will explain all of those as we go.
As an extra, we also get:
- redux devtools
- combine reducers
Step 3: Creating the Redux Store
You are going to think of the store as the entire state of your application.
Create a new file called store.js
in the src
directory and add the following code:
import { configureStore } from '@reduxjs/toolkit';
import cartReducer from './features/cart/cartSlice';
export default configureStore({
reducer: {
cart: cartReducer,
},
});
This code imports the configureStore
function from Redux Toolkit and our cartReducer
from a file that we will create in the next step. It then exports the configured Redux store.
Step 4: Setup Provider
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
// import store and provider
import { store } from './store';
import { Provider } from 'react-redux';
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>,
document.getElementById('root')
);
Next, we import the store and Provider and then wrap the entire application with the Provider and pass the store as props.
Step 5: Creating the Cart Slice
I want you to think of slice as the features of your application for example increasing and decreasing the number of items in the cart or triggering a modal. The bigger the application the more features we are going to have. And in the Redux toolkit land, it is called slice.
To setup a slice, a common convention is to create a features folder, Create a new directory called features
in the src
directory, and inside it, create a new directory called cart
. Inside the cart
directory, create a new file called cartSlice.js
and add the following code:
import { createSlice } from '@reduxjs/toolkit';
export const cartSlice = createSlice({
name: 'cart',
initialState: [],
reducers: {
addToCart: (state, action) => {
const index = state.findIndex(item => item.id === action.payload.id);
if (index !== -1) {
state[index].quantity += 1;
} else {
state.push({ ...action.payload, quantity: 1 });
}
},
removeFromCart: (state, action) => {
const index = state.findIndex(item => item.id === action.payload);
if (index !== -1) {
state.splice(index, 1);
}
},
incrementQuantity: (state, action) => {
const index = state.findIndex(item => item.id === action.payload);
if (index !== -1) {
state[index].quantity += 1;
}
},
decrementQuantity: (state, action) => {
const index = state.findIndex(item => item.id === action.payload);
if (index !== -1) {
state[index].quantity -= 1;
if (state[index].quantity === 0) {
state.splice(index, 1);
}
}
},
},
});
export const { addToCart, removeFromCart, incrementQuantity, decrementQuantity } = cartSlice.actions;
export default cartSlice.reducer;
export const selectCart = state => state.cart;
export const selectCartTotal = state => state.cart.reduce((total, item))
Let's understand what's happening in the code above.
The cartSlice.js contains the initial state of an empty array and several reducer functions for adding, removing, and updating items in the cart.
The addToCart
reducer function checks if the item being added already exists in the cart by checking if its id
matches any existing items in the cart. If it does, it increments the quantity
of that item. If not, it adds the new item to the cart with a quantity
of 1.
The removeFromCart
reducer function removes an item from the cart by finding the index of the item with the id
that matches the payload
of the action being dispatched. If the item is found, it is removed from the cart using the splice
method.
The incrementQuantity
reducer function increments the quantity
of an item in the cart by finding the index of the item with the id
that matches the payload
of the action being dispatched and incrementing its quantity
.
The decrementQuantity
reducer function decrements the quantity
of an item in the cart by finding the index of the item with the id
that matches the payload
of the action being dispatched and decrementing its quantity
. If the quantity
of the item becomes 0, the item is removed from the cart using the splice
method.
The cartSlice.actions
object exports the four reducer functions defined in the reducers
property of the createSlice
function.
The selectCart
and selectCartTotal
functions are selectors that are used to access the cart
slice of the Redux store and compute the total price of all items in the cart.
Finally, the export default
statement exports the cartSlice.reducer
function, which is used to create the Redux store for the shopping cart.
Overall, this code defines the structure and behavior of the shopping cart slice.
Step 5: Creating the UI Components
Now that we have our Redux store and cartSlice
set up, we can start building the UI components for our shopping cart application. In the src
directory, create a new directory called components
. Inside the components
directory, create a new file called Cart.js
and add the following code:
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import {
selectCart,
selectCartTotal,
addToCart,
removeFromCart,
incrementQuantity,
decrementQuantity,
} from '../features/cart/cartSlice';
const Cart = () => {
const cart = useSelector(selectCart);
const total = useSelector(selectCartTotal);
const dispatch = useDispatch();
const handleAddToCart = item => {
dispatch(addToCart(item));
};
const handleRemoveFromCart = id => {
dispatch(removeFromCart(id));
};
const handleIncrementQuantity = id => {
dispatch(incrementQuantity(id));
};
const handleDecrementQuantity = id => {
dispatch(decrementQuantity(id));
};
return (
<div>
<h2>Shopping Cart</h2>
{cart.length === 0 ? (
<p>Your cart is empty.</p>
) : (
<>
<ul>
{cart.map(item => (
<li key={item.id}>
<div>
<span>{item.name}</span>
<button onClick={() => handleRemoveFromCart(item.id)}>-</button>
<span>{item.quantity}</span>
<button onClick={() => handleIncrementQuantity(item.id)}>+</button>
<span>${item.price * item.quantity}</span>
</div>
</li>
))}
</ul>
<p>Total: ${total}</p>
</>
)}
<button onClick={() => handleAddToCart({ id: 1, name: 'Item 1', price: 10 })}>Add Item 1 to Cart</button>
<button onClick={() => handleAddToCart({ id: 2, name: 'Item 2', price: 20 })}>Add Item 2 to Cart</button>
</div>
);
};
export default Cart;
This code defines a Cart
the component that uses the useSelector
and useDispatch
hooks from React Redux to access the cart
state and dispatch actions to the Redux store. It also defines several event handlers for adding and removing items from the cart and updating item quantities.
Step 6: Rendering the App
Finally, we need to render our Cart
component in our App
component. Open up the App.js
file in the src
directory and replace the existing code with the following:
import React from 'react';
import './App.css';
import Cart from './components/Cart';
function App() {
return (
<div className="App">
<Cart />
</div>
);
}
export default App;
This code simply renders our Cart
component in the App
component.
Step 7: Running the App
With our project setup and our code written, we can now run our shopping cart application. Open up your terminal and run the following command:
npm start
This will start the development server and open up the shopping cart application in your browser.
Conclusion
In this article, we learned how to use Redux Toolkit to simplify the process of creating a Redux store and how to build a shopping cart application using React and Redux Toolkit. We created a cartSlice
that contained the reducer, actions, and selectors