I’m using redux-tlk for my ecommerce site. This is my cartSlice.js for products that are added to the cart. So far, it’s working well when I manipulate the quantity with increment/decrement buttons inside this cartProduct component which displays a list of products in the cart.
Then, I created ProductDetails component which is rendered when a product is clicked to view the details. Inside, I also try to implement increment/decrement buttons with add to cart button.
Although my AddToCart btn here works well, I can’t display productQuantity. I’m calling the same function from cartSlice.js but it shows that the value is undefined when I try to output product.cartQuantity.
Can somebody check it for me and help me figure out what I’m missing in my code?
//CartSlice.js
import { createSlice } from "@reduxjs/toolkit";
import { toast } from "react-toastify";
const initialState = {
cartItems:localStorage.getItem('cartItems') ? JSON.parse(localStorage.getItem('cartItems')) : [],
totalQuantity:0,
totalAmount:0,
}
const cartSlice = createSlice({
name:'cart',
initialState,
reducers :
{
addToCart(state,action) {
const cartItemIndex = state.cartItems.findIndex(
(item) => item.id === action.payload.id);
if(cartItemIndex >= 0) {
state.cartItems[cartItemIndex].cartQuantity += 1;
toast.info(`${action.payload.name} is added to the cart again!`,
{position:'top-right' })
}
else {
const tempProduct = {...action.payload, cartQuantity:1};
state.cartItems.push(tempProduct);
toast.success(`${action.payload.name} is added to the cart!`,
{position:'top-right'})
}
localStorage.setItem("cartItems",JSON.stringify(state.cartItems))
},
removeCartItem(state,action) {
const inCartItems = state.cartItems.filter(
cartItem => cartItem.id!==action.payload.id
);
state.cartItems=inCartItems;
localStorage.setItem('cartItems', JSON.stringify(state.cartItems))
toast.error(`${action.payload.name} is removed from the cart!`,
{position:'bottom-right'})
},
decreaseCartQuantity(state,action ) {
const itemIndex= state.cartItems.findIndex(
cartItem => cartItem.id === action.payload.id
)
if(state.cartItems[itemIndex].cartQuantity > 1) {
state.cartItems[itemIndex].cartQuantity-= 1;
}
else if(state.cartItems[itemIndex].cartQuantity === 1) {
const inCartItems = state.cartItems.filter(
cartItem => cartItem.id!==action.payload.id
);
state.cartItems=inCartItems;
toast.error(`${action.payload.name} is removed from the cart!`,
{position:'bottom-right'})
}
localStorage.setItem('cartItems', JSON.stringify(state.cartItems))
},
increaseCartQuantity(state,action) {
const itemIndex= state.cartItems.findIndex(
cartItem => cartItem.id === action.payload.id
)
state.cartItems[itemIndex].cartQuantity+= 1;
localStorage.setItem('cartItems', JSON.stringify(state.cartItems))
},
clearAllCart(state) {
state.cartItems = [];
toast.error(`The cart is cleared!`,
{position:'bottom-right'});
localStorage.setItem('cartItems', JSON.stringify(state.cartItems));
},
getTotal(state) {
let {total,quantity} = state.cartItems.reduce((cartTotal, cartItem)=>{
const {price,cartQuantity} = cartItem;
const itemTotal = price * cartQuantity;
cartTotal.total += itemTotal;
cartTotal.quantity += cartQuantity
return cartTotal;
}, {
total:0,
quantity:0,
});
state.totalQuantity = quantity;
state.totalAmount = total;
}
}
})
export const {addToCart, removeCartItem, decreaseCartQuantity,
increaseCartQuantity,clearAllCart, getTotal} = cartSlice.actions;
export default cartSlice.reducer;
//CartProduct.js
import styled from '@emotion/styled'
import { Add, ArrowBackIos, Remove } from '@mui/icons-material'
import { Box,Button,IconButton,Paper,Table,TableBody,TableCell,tableCellClasses,TableContainer,TableHead,TableRow,TextField,Typography, useMediaQuery } from '@mui/material'
import React from 'react'
import {useDispatch, useSelector } from 'react-redux'
import { Link} from 'react-router-dom'
import { clearAllCart, decreaseCartQuantity, getTotal, increaseCartQuantity } from '../rtk/app/features/cartSlice'
import { theme } from '../style/theme'
export const CartProduct = () => {
const cart = useSelector((state)=>state.cart);
const cartItems = useSelector((state)=>state.cart.cartItems);
const totalQuantity = useSelector(state=> state.cart.totalQuantity);
const totalAmount = useSelector(state=>state.cart.totalAmount);
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
const isTablet = useMediaQuery(theme.breakpoints.down('md'));
const isLaptop = useMediaQuery(theme.breakpoints.down('lg'));
const SHIPPING_FEES = 7.99;
const dispatch = useDispatch();
function handleBack() {
window.history.back();
}
function handleRemoveItem(cartItem) {
dispatch(decreaseCartQuantity(cartItem));
}
function handleAddItem(cartItem) {
dispatch(increaseCartQuantity(cartItem));
}
function clearCart() {
dispatch(clearAllCart());
}
React.useEffect(()=>{
dispatch(getTotal());
},[cart,dispatch])
console.log(cartItems);
return (
//styling components
{ cartItems.length>=1 &&
<TableContainer component={Paper}>
// some content
<IconButton size='small' onClick={()=>{handleAddItem(cartItem)}}>
<Add color='primary'/>
</IconButton>
<input type='text' className='quantity-value'
pattern='[0-9]{2}'
value={cartItem.cartQuantity}/>
<IconButton size='small' onClick={()=>{handleRemoveItem(cartItem)}}>
<Remove color='primary'/>
</IconButton>
</StyledTableCell>
<StyledTableCell align='center' width='30%'>
$ {cartItem.price}
</StyledTableCell>
<StyledTableCell align='center'>
$ {parseFloat(cartItem.price* cartItem.cartQuantity).toFixed(2)}
</StyledTableCell>
</StyledTableRow>
))}
<TableRow>
<TableCell rowSpan={4} />
<TableCell colSpan={2}>Subtotal</TableCell>
<TableCell align="center" width='100%' sx={{
fontWeight:'bold',
}}>$ {parseFloat(totalAmount).toFixed(2)}</TableCell>
</TableRow>
<TableRow>
<TableCell colSpan={2}>Total Quantity</TableCell>
<TableCell align="center" sx={{
fontWeight:'bold',
}}>{totalQuantity}</TableCell>
</TableRow>
<TableRow>
<TableCell colSpan={2}>Shipping Fees</TableCell>
<TableCell align="center" sx={{
fontWeight:'bold',
}}>$ {SHIPPING_FEES}</TableCell>
</TableRow>
<TableRow >
<TableCell colSpan={2}>Total</TableCell>
<TableCell align="center" sx={{
fontWeight:'bold',
}}>$ {parseFloat(SHIPPING_FEES + totalAmount).toFixed(2) }</TableCell>
</TableRow>
</TableBody>
</Table>
//This cardProduct.jsx works fine calling the same function from cartSlice.js
//ProductDetails.jsx
import { Add, ArrowBackIos, Circle, Remove } from '@mui/icons-material';
import { Box, Button, Chip, IconButton, Rating, Tooltip, Typography, useMediaQuery } from '@mui/material'
import React from 'react'
import { useDispatch, useSelector } from 'react-redux';
import { useLocation } from 'react-router-dom';
import { addToCart, decreaseCartQuantity, increaseCartQuantity } from '../rtk/app/features/cartSlice';
import { theme } from '../style/theme'
export const ProductDetails = () => {
const cart = useSelector((state)=>state.cart);
const cartItems = useSelector((state)=>state.cart.cartItems);
const [product, setProduct] = React.useState(null);
const location = useLocation();
const dispatch = useDispatch();
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
React.useEffect(() => {
const { state } = location;
setProduct(state ? state.product : null);
}, [location,setProduct]);
function handleRemoveItem(product) {
dispatch(decreaseCartQuantity(product));
}
function handleAddItem(product) {
dispatch(increaseCartQuantity(product));
}
const handleAddToCart = (product) => {
dispatch(addToCart(product));
}
//console.log(product.cartQuantity);
//If I didn't comment it out, the program crashed, claiming product is null. After I rerun the program, it outputs undefined.
return (
<Box pt={10} px={2} mt={5}>
<IconButton onClick={()=>window.history.back()} size='medium'>
<ArrowBackIos color='tertiary'/>
</IconButton>
{product!=null &&
<Box sx= {{width:'80%', margin:'1em auto 0 auto', boxShadow:3, padding:'1em 0 0.5em 0'}}>
<Typography variant='h6' color='secondary' textAlign='center'>
{product.name}
</Typography>
<Box display='flex'
justifyContent='center'
flexDirection={isMobile? 'column' : 'row'}>
<Box py={1} mx={2} width='40%' display='flex'
justifyContent='center' alignItems='center'
flexDirection='column'>
<Box component='img'
sx={{objectFit:'contain'}}
src={product.image_link}
alt={product.name}
width='50%'/>
<Typography variant='caption' component='h6'
color={theme.palette.primary.light}>
Brand: {product.brand}
</Typography>
</Box>
<Box py={2} mx={2} width='60%'>
<Typography variant='caption' component='h6'
color={theme.palette.primary.light}
gutterBottom pb={2}
sx={{width:isMobile? '90%' : '80%'}}>
Description:<br/>
{product.description}
</Typography>
{product.category &&
<Typography variant='caption' component='h6'
color ={theme.palette.primary.light} gutterBottom>
Category : {product.category}</Typography>}
<Typography variant='caption' component='h6'
color ={theme.palette.primary.light} gutterBottom>
Rating :
<Rating value={product.rating} name='product_rating' readOnly
size='small' sx={{verticalAlign:'middle'}}/>
</Typography>
{product.product_colors.length >=1 &&
<Typography variant='caption' component='h6'
color ={theme.palette.primary.light}>Available Colours :
{product.product_colors.map((color)=> (
<Tooltip title={color.colour_name}>
<Circle sx={{color:color.hex_value, verticalAlign:'middle'}}/>
</Tooltip>
))}
</Typography>}
{product.tag_list.length >=1 &&
<Typography variant='caption' component='h6'
color={theme.palette.primary.light}>Product Tags :
{product.tag_list.map((tag)=>(
<Chip label={tag} variant='outlined'/>
))}
</Typography>}
<IconButton size='small' onClick={()=>{handleAddItem(product)}}>
<Add color='primary'/>
</IconButton>
<input type='number' className='quantity-value'
pattern='[0-9]{2}'
value={product.cartQuantity} readOnly/>
<IconButton size='small' onClick={()=>{handleRemoveItem(product)}}>
<Remove color='primary'/>
</IconButton>
<Button sx={{background:theme.palette.secondary.main,
transition:'background 1s ease-in-out',
padding:'0.5em 2em',
margin:'1em',
color:theme.palette.textColor.main,
'&:hover': {
background:theme.palette.primary.main,
}}}
onClick={()=>{handleAddToCart(product)}}>
Add to Cart
</Button>
</Box>
</Box>
</Box>}
</Box>
)
}