Wishlist Functionality using Redux Toolkit in React
Wishlist is one of the important concepts of an E-commerce platform. In this article, we are going to learn how to implement Wishlist functionality using React JS and Redux Toolkit.
Preview of final output: Let us have a look at how the final output will look like.
Prerequisites
Approach to create Wishlist Functionality:
- Install Redux Toolkit:Use npm or yarn to install Redux Toolkit:
npm install @reduxjs/toolkit
oryarn add @reduxjs/toolkit
. - Create Wishlist Slice:Use
createSlice
to define a Redux slice for the Wishlist.Specify the initial state and create reducers for actions likeaddToWishlist
andremoveFromWishlist
. - Combine Reducers:Combine the Wishlist reducer with other reducers, if any, using
combineReducers
. - Configure Redux Store:Utilize
configureStore
to set up the Redux store and pass the combined reducer. - Dispatch Actions in Components:Use
useDispatch
anduseSelector
hooks fromreact-redux
in components.Dispatch actions (e.g.,addToWishlist
,removeFromWishlist
) based on user interactions.
Steps to Create and Configure React App with redux toolkit
Step 1: Create a react app using command “npx create-react-app app-name”.
npx create-react-app app-name
Step 2: Install the required dependencies
npm install react-redux @reduxjs/toolkit
npm install react-hot-toast
npm i react-router-dom react-icons
Project Structure:
The updated dependencies in package.json file will look like:
"dependencies": {
"@reduxjs/toolkit": "^2.0.1",
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hot-toast": "^2.4.1",
"react-icons": "^4.12.0",
"react-redux": "^9.0.4",
"react-router-dom": "^6.21.1",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
}
Example: Create the folder structure and insert the following files accordingly.
Javascript
// Wishlist/index.jsx import { useSelector } from "react-redux" import RenderwishlistItems from "./RenderWishlistItems" export default function Wishlist() { const {total, totalItems} = useSelector((state) => state.wishlist) return ( <div > <h1>Your wishlist</h1> <p >{totalItems} Items in wishlist</p> { total>0 ? ( <div > <RenderwishlistItems /> </div> ) : ( <p>Your wishlist is empty</p> ) } </div> ) } |
Javascript
// Wishlist/RenderWishlistItems.jsx import React from 'react' import { useDispatch, useSelector } from 'react-redux' import {RiDeleteBin6Line} from "react-icons/ri" import { removeFromWishlist } from '../Slices/WishlistSlice' export default function RenderWishlistItems() { const {wishlist} = useSelector((state) => state.wishlist) const dispatch = useDispatch(); return ( <div> { wishlist.map((dataObj, index) => ( <div key={index} className= { `flex w-full flex-wrap items-start justify-between gap-6 ${index !== wishlist.length - 1 && "border-b border-b-richblack-400 pb-6" } ${index !== 0 && "mt-6" } ` }> <div> <div> {dataObj.title} </div> <img src={dataObj.image} width={200} height={150} alt= "" /> <p> {dataObj.description} </p> </div> <div> <button onClick={ () => dispatch(removeFromWishlist(dataObj._id)) }> Remove from wishlist <RiDeleteBin6Line size={20}/> </button> </div> </div> )) } </div> ) } |
Javascript
// Components/Product_card.jsx import React from 'react' import { addToWishlist } from '../Slices/WishlistSlice' ; import { useDispatch } from 'react-redux' ; import { CiHeart } from "react-icons/ci" ; const Product_card = ({ dataObj }) => { const dispatch = useDispatch(); const handleAddToWishlist = () => { console.log( "dispatching add to Wishlist" ) dispatch(addToWishlist(dataObj)); return ; } return ( <div style={{ display: 'flex' , flexWrap: 'wrap' , gap: '70px' , justifyContent: 'space-around' , marginTop: '70px' }}> <div style={{ width: "15em" , backgroundColor: "#35D841" , padding: 2, borderRadius: 10, marginBlock: 10, }}> <p style={{ fontSize: 20, color: 'black' }}> {dataObj.title} </p> <img src={dataObj.image} alt= "" height={200} width={200} style={{ borderRadius: "15px" , }}/> <p>{dataObj.description}</p> <CiHeart onClick={handleAddToWishlist} size={35} color= 'white' /> </div> </div> ) } export default Product_card |
Javascript
// Pages/Home.jsx import React, { useEffect, useState } from 'react' import { FaHeart } from "react-icons/fa" ; import { Link } from 'react-router-dom' ; import Product_card from '../Components/Product_card' ; const items = [ { "id" : 1, "title" : "w3wiki bag" , "price" : 109.95, "description" : "Your perfect pack for everyday use and walks in the forest." , "category" : "bag" , "image" : "https://practice.w3wiki.net/_next/image?url=https%3A%2F%2Fmedia.w3wiki.net%2Fimg-practice%2FMaskGroup31-1651641966.png&w=640&q=75" , "rating" : { "rate" : 3.9, "count" : 120 } }, { "id" : 2, "title" : "w3wiki tshirt" , "price" : 22.3, "description" : "Slim-fitting style,black tshirt. From w3wiki" , "category" : "men's clothing" , "image" : "https://practice.w3wiki.net/_next/image?url=https%3A%2F%2Fmedia.w3wiki.net%2Fimg-practice%2FGroup7895-1651644285.png&w=640&q=75" , "rating" : { "rate" : 4.1, "count" : 259 }, }, { "id" : 3, "title" : "w3wiki bag" , "price" : 109.95, "description" : "Your perfect pack for everyday use and walks in the forest." , "category" : "bag" , "image" : "https://practice.w3wiki.net/_next/image?url=https%3A%2F%2Fmedia.w3wiki.net%2Fimg-practice%2FMaskGroup31-1651641966.png&w=640&q=75" , "rating" : { "rate" : 3.9, "count" : 120 } }, ] const Home = () => { return ( <div> <Link to={ "/wishlist" }> <FaHeart size={40} color= "#35D841" /> </Link> <div style={{ display: 'Flex ', justifyContent: ' space-around', }}> {items.map((dataObj, index) => { return ( <Product_card dataObj={dataObj} /> ) })} </div> </div> ) } export default Home |
Javascript
// Reducer/index.jsx import { combineReducers } from "@reduxjs/toolkit" ; import wishlistReducer from "../Slices/WishlistSlice" const rootReducer = combineReducers({ wishlist: wishlistReducer, }) export default rootReducer |
Javascript
// Slices/WishlistSlice.jsx import { createSlice } from "@reduxjs/toolkit" ; import { toast } from "react-hot-toast" ; const initialState = { wishlist: localStorage.getItem( "wishlist" ) ? JSON.parse(localStorage.getItem( "wishlist" )) : [], total: localStorage.getItem( "total" ) ? JSON.parse(localStorage.getItem( "total" )) : 0, totalItems: localStorage.getItem( "totalItems" ) ? JSON.parse(localStorage.getItem( "totalItems" )) : 0, } const wishlistSlice = createSlice({ name: "wishlist" , initialState, reducers: { addToWishlist: (state, action) => { const item = action.payload state.wishlist.push(item) state.totalItems++ state.total += item.price localStorage .setItem( "wishlist" , JSON.stringify(state.wishlist)) localStorage .setItem( "total" , JSON.stringify(state.total)) localStorage .setItem( "totalItems" , JSON.stringify(state.totalItems)) toast.success( "Item added to wishlist" ) }, removeFromWishlist: (state, action) => { const itemId = action.payload const index = state.wishlist .findIndex( (item) => item._id === itemId) if (index >= 0) { state.totalItems-- state.total -= state.wishlist[index].price state.wishlist.splice(index, 1) localStorage .setItem( "wishlist" , JSON.stringify(state.wishlist)) localStorage .setItem( "total" , JSON.stringify(state.total)) localStorage .setItem( "totalItems" , JSON.stringify(state.totalItems)) toast .success( "Item removed from wishlist" ) } }, } }) export const { addToWishlist, resetWishlist, removeFromWishlist } = wishlistSlice.actions export default wishlistSlice.reducer; |
Javascript
// App.js import './App.css' ; import { Route, Routes } from "react-router-dom" ; import Wishlist from "./Wishlist/index" import Home from "./Pages/Home" ; function App() { return ( <div className= "App" > <Routes> <Route path= "/wishlist" element={<Wishlist />} /> <Route path= "/" element={<Home />} /> </Routes> </div> ); } export default App; |
Javascript
// index.js import React from 'react' ; import ReactDOM from 'react-dom/client' ; import './index.css' ; import App from './App' ; import reportWebVitals from './reportWebVitals' ; import rootReducer from './Reducer' ; import { Provider } from "react-redux" ; import {configureStore} from "@reduxjs/toolkit" import { BrowserRouter } from 'react-router-dom' ; import { Toaster } from 'react-hot-toast' ; const store = configureStore({ reducer: rootReducer, }) const root = ReactDOM.createRoot(document.getElementById( 'root' )); root.render( <React.StrictMode> <Provider store={store}> <BrowserRouter> <App /> <Toaster/> </BrowserRouter> </Provider> </React.StrictMode> ); reportWebVitals(); |
CSS
/* App.css */ .App { text-align : center ; } .App-logo { height : 40 vmin; pointer-events: none ; } @media (prefers-reduced-motion: no-preference) { .App-logo { animation: App-logo-spin infinite 20 s linear; } } .App-header { background-color : #282c34 ; min-height : 100 vh; display : flex; flex- direction : column; align-items: center ; justify- content : center ; font-size : calc( 10px + 2 vmin); color : white ; } .App-link { color : #61dafb ; } @keyframes App-logo-spin { from { transform: rotate( 0 deg); } to { transform: rotate( 360 deg); } } |
CSS
/* index.css */ 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; } code { font-family : source-code-pro, Menlo, Monaco, Consolas, 'Courier New' , monospace ; } |
Steps to Run the Application:
npm start
Output: go to this url http://localhost:3000/
Contact Us