8 min read

React Redux Tutorial With A Simple Project

Last Update: 1 July, 2022

Managing state in react is not very hard when your projetcs are small enough. As you projetcs get bigger you realize that lifting state up or passing state down just creates clutter and makes your code harder to read and understand. Redux at this point comes to the rescue, nowadays with even less boilerplate code. Let's start!

In this post we will be creating a simple e-commerce app using react and react-redux. I believe it will be easier to understand this way. I will also be explaining the pieces of code as we go over them. If you feel like you are stuck somewhere and if you see an error you can just visit the repository.

§Creating Our Project

We will start our project with a simple npx command.

npx create-react-app react-redux-project

When the project setup is done we can go ahead and install the dependencies we will be using. We of course need to install react-redux and to handle routing with react-router-dom.

npm i react-router-dom react-redux

§Clean Up The Starter Project

First of all let's delete the logo as we won't be using it anywhere in our project. We can delete App.css, App.js and App.test.js as well. As we will be creating our components and pages in folders and files we will soon create. Now we can delete everything but the wrapper div inside the index.js file. When we are done our index.js file should look like this :

import React from 'react'; import ReactDOM from 'react-dom/client'; import './index.css'; import reportWebVitals from './reportWebVitals'; const root = ReactDOM.createRoot(document.getElementById('root')); root.render( <React.StrictMode> <div>Test</div> </React.StrictMode> ); // If you want to start measuring performance in your app, pass a function // to log results (for example: reportWebVitals(console.log)) // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals reportWebVitals();

If everything has gone well up to this point, when you execute the command bash npm start you should see the Test on your screen now. Now we are ready to write our code.

§Project Details And Planning

I think a typical e-commerce website is a good example. We will be able to see how easy it is handle state using the typical redux functionality. In our home page we are going to keep 9 items inside a grid layout. Each item will have a button that will allow us to add the selected item in our cart. We will be able to see the quantity of the items we have in our cart on the navbar. We will keep count with a simple badge in link to cart page.

A personal note, before the recent changes that redux went through the boilerplate code to just get things started was just too much work. I would personally avoid using redux whenever I could but you will see that it's much simpler now. Of course it's still up to you to decide whether you need it or not.

As our project is small and simple, I didn't want to make it over complicated by adding a css library. We wll be creating our css files inside the folders we will create for each page and component. This way our code will be easier to read and understand.

In src folder create page and components folders. We will be creating our pages and components in these folders.

§Creating The Store

You can think of store as where all the state and the logic to set our state exists. Keeping the store seperate like this allows us to reach it from any component we want. The only way to change the state inside it is to dispatch an action on it.

Create a folder called app inside the src folder. Inside the folder create store.js file. The code for this file is very simple as you can see:

import { configureStore } from '@reduxjs/toolkit' import shopReducer from '../slice/shopSlice' export default configureStore({ reducer: { shop: shopReducer }, })

As we don't have our reducer and slice yet you will be seeing an error after creating the store. Don't worry about it for now as we will soon create it.

We have created the store for our shop. Now we can handle all the logic necessary to actually make stuff happen.

§Creating The Slice

Creating a slice with redux toolkit is very simple. It wasn't as simple as this before and I think it was the main reason people (me included) avoided using redux. We had to create actions, action types etc. It was just too bothersome.

We will be using createSlice function to, well, create a slice. This function accepts an initial state, reducer functions to be dispatched and a name for our slice. That's it! Redux toolkit takes care of generating action creators and types.

Inside the src folder, we will create a new file named shopSlice. I believe the code is very self explanatory but I also added comments to make easier to understand. The full code for this file is here:

import { createSlice } from '@reduxjs/toolkit' // this object will be used as our initial state. const state = { // Our cart is initally empty cart: [], // Our array that we are going to show in our home page // We are just going to use the colors as items to sell items: [ { id: 1, name: "cyan", price: 10.99}, { id: 2, name: "teal", price: 12.99}, { id: 3, name: "yellow", price: 14.99}, { id: 4, name: "lime", price: 12.99}, { id: 5, name: "pink", price: 6.99}, { id: 6, name: "purple", price: 2.99}, { id: 7, name: "turquoise", price: 4.99}, { id: 8, name: "red", price: 18.99}, { id: 9, name: "gold", price: 20.99}, ] } export const shopSlice = createSlice({ name: 'shop', initialState: state, reducers: { // action is the way of sending data with our dispatches to store to change our state // action.payload in our case will only have our item ids, this way we will be able to // set our state addItemToCart: (state, action) => { // First let's check if the item exists in the cart let item = state.cart.filter(x => x.id === action.payload)[0] // If we have item in the cart, then we will just increment it's quantity if(item) { let newQuantity = item.quantity + 1; // We are using spread operator to create our new item with an incremented quantity item = {...item, quantity: newQuantity} let i = state.cart.findIndex(i => i.id === action.payload) // We are using slice method to cut out the parts until and after the item of which we are // changing the quantity, // Again using the spread operator to create our new cart state.cart = [...state.cart.slice(0,i), item,...state.cart.slice(i+1)] } // If we don't have the item in our cart else { // We need to get the item and add a new key, quantity let i = state.items.filter(x => x.id === action.payload)[0]; i = {...i, quantity: 1 }; // Finally add it to our cart state.cart = [...state.cart, i] } }, removeFromCart: (state, action) => { // Filter the item out state.cart = state.cart.filter(i => i.id !== action.payload) }, // Decrement logic that we will need in our cart decrementQuantity: (state, action) => { // Loop through the array using forEach method and decrement the quantity of the Item // As you can see we didn't need to create a new array in a non mutating way, like we did // for the increment method. Of course it's possible to use a method like this one here // for adding an item to our cart. state.cart.forEach(i => { if(i.id === action.payload) { if(i.quantity > 1) { i.quantity -= 1; }else{ // if there's only one remove the item state.cart = state.cart.filter(i => i.id !== action.payload) } } }) }, clearCart: (state) => { // Empty the cart state.cart = []; } }, }) // Selectors are going to be used to reach the state. We can think of them as getters export const selectCart = (state) => state.shop.cart; export const selectItems = (state) => state.shop.items; // Finally we are exporting all the actions to be dispatched export const { addItemToCart, removeFromCart, decrementQuantity , clearCart } = shopSlice.actions // We are exporting our reducer to be used in store export default shopSlice.reducer

That was all the code necessary to run our simple e-commerce website. As I also wrote in the comments above you can change the logic in a mutating manner. That method is alo totally safe now as redux takes care of the logic under the hood for us.

§Provider Wrapper

In our index.js file we need to import Provider from react redux. Wrapping our app with the Provider makes the store visible and allow us to interact with the store. The code is also very simple, with this latest addition our index.js looks like this:

import React from 'react'; import ReactDOM from 'react-dom/client'; import './index.css'; import reportWebVitals from './reportWebVitals'; import store from './app/store'; import { Provider } from 'react-redux'; const root = ReactDOM.createRoot(document.getElementById('root')); root.render( <React.StrictMode> <Provider store={store}> <div>Test</div> </Provider> </React.StrictMode> ); // If you want to start measuring performance in your app, pass a function // to log results (for example: reportWebVitals(console.log)) // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals reportWebVitals();

§Start Building The Routes

We will handle routing with the help of the react-router-dom package. This package will allow us to use a wrapper layout for our pages and this way you will see that the linking into our own pages will look seemless. We won't be reloading the whole page but only the parts that are different. I'm not going get any deeper into routing, I suggest that you read the documenttation here if you wanna learn more about it.

We can create basic pages and our navbar in order to test our router functionality. Let's go ahead and create Cart.jsx and Home.jsx files inside the pages folder. We don't need anything cool here in these pages so far. You can just create a react component here. These pages will be later used to import in all the components we will use in our pages. Seperation of our pages and components this way makes them esaier to understand later and I believe it's just good practice.

If you have created the pages we can now create our navbar folder. In this folder we will create a Navbar.jsx file and a Navbar.css file. We will import two elements (not really sure what to call them), Lİnk and Outlet. Link will replace the a tags for internal linking and provide us the seemless routing experience. This way we won't be realoading the parts that remain the same like our Navbar here. The Outlet will be used to determine where the wrapped components will appear inside our Navbar component. Seeing described functionality in action will help us understand better.

In our Navbar I wanted to add a simple badge like counter that will show us the quantity of the items in our cart. To achieve this, we are going to use a selector. We will select the cart and calculate the quantity of items in a useEffect hook. We could create this functionality and logic using redux. I just did it this way. If you wanna create a simple challange for yourself and test your redux knowledge feel free to implement the logic using redux.

In order to make this code easier to understand I added some comments down below. The finished code for our Navbar will look something like this:

import React, { useState, useEffect } from 'react' import { Outlet, Link } from 'react-router-dom' import './Navbar.css' import { useSelector } from 'react-redux'; import { selectCart } from '../../slice/shopSlice'; const Navbar = () => { const cart = useSelector(selectCart) const [cartQuantity, setCartQuantity] = useState(0); useEffect(() => { if(cart.length >= 0){ let n = 0; cart.map(x => n += x.quantity) setCartQuantity(n) } }, [cart, cartQuantity]) return (<> <div className='navbar-wrapper'> <Link className='nav-link' to="/">Home</Link> <Link className='nav-link' to="/cart">Cart <span className='length-badge'>{cartQuantity}</span></Link> </div> {/* Using Outlet here will make the components that we are going to wrap with our Navbar appear here. */} <Outlet /> </>) } export default Navbar

Now we can style our Navbar. Of course feel free to use your own style if you won't like mine, Imust admit I'm not the best web designer. Now in Navbar.css file add this code below:

.navbar-wrapper { height: 100px; text-align: end; padding: 20px 40px; font-size: 1.4rem; position: fixed; left:0; right: 0; font-weight: bold; background: linear-gradient(45deg, rgba(255,255,255,0), rgba(255, 255, 255, .1)); z-index: 100; } .nav-link { color: rgb(96, 1, 1); text-decoration: none; margin-right: 40px; font-size: 1.8rem; transition: 0.2s; } .nav-link:hover { color: rgb(130, 62, 62); } .length-badge { font-size: 0.9rem; }

We won't need to change our Navbar anymore so we can now also handle the index.js file and test our router. The full code for our router is down below:

import React from 'react'; import ReactDOM from 'react-dom/client'; import './index.css'; import reportWebVitals from './reportWebVitals'; import store from './app/store'; import { Provider } from 'react-redux'; import { BrowserRouter, Routes, Route } from 'react-router-dom'; import Navbar from './components/navbar/Navbar'; import Home from './pages/Home'; import Cart from './pages/Cart'; const root = ReactDOM.createRoot(document.getElementById('root')); root.render( <React.StrictMode> <Provider store={store}> <BrowserRouter> <Routes> <Route path="/" element={<Navbar/>}> <Route index element={<Home />} /> <Route path="cart" element={<Cart/>} /> </Route> </Routes> </BrowserRouter> </Provider> </React.StrictMode> ); // If you want to start measuring performance in your app, pass a function // to log results (for example: reportWebVitals(console.log)) // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals reportWebVitals();

The full code for the index.css is here as well:

* { box-sizing: border-box; padding: 0; margin: 0; } img { width: 100%; } body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; min-height: 100vh; } code { font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; }

If everything has gone well up to this point you should be able to visit the 2 pages we created using links on our navigation bar.

§Now We Can Create Rest Of Our Components

I think it would make more sense to start creating the components we will use in our home page. We need a banner that tells the user in what page they are at the moment, and we need to show all the available items to the user. We can use this banner inside the cart page so we can create a component that takes the text as a prop to show. Let's start by creating the banner.

Inside the components folder create a banner folder. In this folder we will create Banner.jsx and Banner.css files. Both of these files are very simple and easy to understand:

// Banner.jsx file import React from 'react' import './Banner.css' const Banner = ({text}) => { return ( <div className='banner-wrapper'> <p className='banner-text'>{text}</p> </div> ) } export default Banner .banner-wrapper { height: 300px; background: teal; } .banner-text { font-size: calc(32px + 2vw); margin-left: 140px; padding-top: 110px; color:rgb(238, 71, 71); text-decoration: under; font-weight: bold; text-shadow: 2px 2px 2px rgb(1, 34, 70); }

Now that this is taken care of we can think of a component that will display of our items. For this functianality we are actually going to need two components. One component that will recieve the items and another component that we can render for each item in the items array.

Let's create items folder inside the components and inside the folder we will create Items.jsx and Items.css files. We only need the selector functionality here as well. We will recieve our items in this component. The full code for this component is as follows:

import React from 'react' import { useSelector } from 'react-redux' import { selectItems } from '../../slice/shopSlice' import './Items.css' const Items = () => { const items = useSelector(selectItems); return ( <div className='items-container'> </div> ) } export default Items

And the code for the css file to make this component not so horrible to look at:

.items-container { display: flex; flex-direction: row; flex-wrap: wrap; align-items: center; justify-content: center; max-width: 960px; margin: -40px auto 260px; background: rgb(221, 254, 255); box-shadow: 2px 2px 32px 2px rgb(0, 0, 0); padding: 40px; border-radius: 10px; }

Simple, right ?

Now that we are recieving our items we can map them using another component. We don't need to use a component as this is a very simple operation but we like to keep our code clean. Go ahead and create item folder inside the components folder. Inside this folder, create Item.jsx and Item.css files. In our Item.jsx file we will create a component in which we will take the item information as a prop and display our items. We will add a button for each item to add the chosen item to our cart. With this button we will dispatch an action and manipulate our cart this way.

The full code for our Item.jsx file is here as follows:

import React from 'react' import './Item.css' import { useDispatch } from 'react-redux' import { addItemToCart } from '../../slice/shopSlice' const Item = ({ name, id, price}) => { const dispatch = useDispatch(); return ( <div className='item-cart'> <div className='color-box' style={{background: name}}></div> <p className='p-name'>{name}</p> <p className='p-price'>{price} $</p> <button onClick={() => dispatch(addItemToCart(id))} className='add-button'>Add to Cart</button> </div> ) } export default Item

Now let's style our component a little bit, the code for Item.css file is here as well:

.item-cart { max-width: 260px; min-width: 200px; margin: 20px; box-shadow: 2px 2px 21px 2px rgb(180, 241, 180); padding: 20px; border-radius: 10px; background: white; } .color-box { padding: 80px; } .p-name { margin-top: 20px; margin-bottom: 10px; color: rgb(16, 57, 99); text-transform: capitalize; font-size: 1.45rem; } .p-price { color: rgb(116, 0, 73); } .add-button { margin-top: 10px; border: none; width: 100%; padding: 10px; background-color:hsl(160, 100%, 75%); border-radius: 15px; transition: .2s; font-size: 1.2rem; box-shadow: 2px 2px 2px 2px teal; } .add-button:hover { background-color: hsl(160, 100%, 24%); color: white; cursor: pointer; } .add-button:active { box-shadow: none; }

This was all the components we need for our home page. So the full code the for our component is here:

import React from 'react' import Banner from '../components/banner/Banner' import Items from '../components/items/Items' const Home = () => { return (<> <Banner text="Home"/> <Items /> </>) } export default Home

§Handling The Cart

We can use the banner component we create for this page as well. That's one of the reasons I love working with components and the components based frameworks are the most popular ones so far.

For this reason we are going to focus on the cart logic, displaying and removing items in our cart. We will handle eveything in on component. Inside the components folder create a folder named inCart. Inside this folder create InCart.jsx and InCart.css files. This component is the most important one yet.

If there is no item in the cart we will show the user a text and a link so they can easily go back to shopping. Otherwise we are going to show our items as list elements. Add buttons for incrementing, decrementing the quantity of an item in the cart. We will add a button to remove an item directly and also a button for clearing the cart by removing all the items. The full code for this component is as follows:

import React from 'react' import './InCart.css' import { useDispatch, useSelector } from 'react-redux' import { removeFromCart, clearCart, selectCart, addItemToCart, decrementQuantity } from '../../slice/shopSlice' import { Link } from 'react-router-dom' const ItemsInCart = () => { const dispatch = useDispatch(); const cart = useSelector(selectCart) return ( <div className='cart-items-container'> {cart.length === 0 ? <p className='empty-cart-text'>Your cart appears to be empty,<Link to="/" className='home-button'> wanna see the items ?</Link></p> : <> <ul className='cart-items-ul'> {cart.map(i => <li className='cart-item-li' key={i.id}> {i.name} <div className='right-align'> <button onClick={() => dispatch(addItemToCart(i.id))}>+</button> {i.quantity} <button onClick={() => dispatch(decrementQuantity(i.id))}>-</button><button onClick={() => dispatch(removeFromCart(i.id))} className='remove-button'>Remove</button> </div> </li>)} </ul> <button onClick={() => dispatch(clearCart())} className='clear-cart-button'>Clear Cart</button> </> } </div> ) } export default ItemsInCart

The code was pretty self-explanatory that's why I didn't add any comments. If you feel like there is part you don't fully understand . Just change the code and write your own, play with it a little. Until you can explain the code to yourself out loud clearly. That's a method I often use when I'm trying to understand if I actually learned something or not.

Now that we have all the logic let's add some styling. This is the css code I wrote for this component:

.cart-items-container { display: flex; flex-direction: row; flex-wrap: wrap; align-items: center; justify-content: center; max-width: 960px; min-height: 600px; margin: -40px auto 260px; background: rgb(221, 254, 255); box-shadow: 2px 2px 32px 2px rgb(0, 0, 0); padding: 40px; border-radius: 10px; } .empty-cart-text { font-size: 1.4rem; color: rgb(0, 0, 0); } .home-button { text-decoration: none; color: rgb(121, 121, 255); } .cart-items-ul { color: rgb(0, 0, 0); list-style: none; width: 100%; } .cart-item-li { font-size: 1.4rem; text-transform: capitalize; border-bottom: 1px solid rgb(0, 0, 0); padding: 20px; width: 100%; display: flex; justify-content: space-between; align-items: center; } .right-align > button { padding: 10px 20px; min-width: 70px; margin: 0 20px; border-radius: 5px; font-size: 1.9rem; } .right-align > button:hover { cursor: pointer; } .remove-button { background-color: red; border: 2px solid white; } .clear-cart-button { padding: 10px 20px; min-width: 70px; margin: 0 20px; font-size: 1.9rem; margin-top: 60px; } .clear-cart-button:hover { cursor: pointer; }

That was easy, right ? And now our Cart.jsx file looks like this:

import React from 'react' import Banner from '../components/banner/Banner' import ItemsInCart from '../components/inCart/InCart' const Cart = () => { return (<> <Banner text="Cart"/> <ItemsInCart /> </>) } export default Cart

§Conclusion

Now we have a finished app in front of us. Implementing redux is definitely not so hard right now. We were able to use a "store" to keep our state and modify the logic. Just think about how you would handle the state changes and pass props So annoying !

I hope you enjoyed reading and creating this project yourself. I hope you have learned something from this post and had fun while doing it, I know I did.

Don't stop here, add a payment functionality, create your items, create a backend, connect to a database etc. make it yours ! Keep on learning !

Ilker Akbiyik