I know similar questions have been asked, but I couldn't find one which I could adapt to my code (possibly cause I am still new to Redux). So, I am editing the cart functionality which was already written and where I already have two functions:
export function calculateTotal(cart) {
let totalUSD = 0;
Object.keys(cart).forEach((itemName) => {
totalUSD += cart[itemName].price * cart[itemName].quantity;
});
return totalUSD.toFixed(2);
}
export function calculateTotal(cart) {
let totalUSD = 0;
Object.keys(cart).forEach((itemName) => {
totalUSD += cart[itemName].price * cart[itemName].quantity;
});
return totalUSD.toFixed(2);
}
export function calculateTotalQuantity(cart) {
let totalQuantity = 0;
Object.keys(cart).forEach((itemName) => {
totalQuantity += cart[itemName].quantity;
});
return totalQuantity;
}
import React from 'react';
import "./Cart.css";
import Navbar from './Navbar.jsx';
import Footer from './Footer.jsx';
import { useDispatch, useSelector } from 'react-redux';
import { changeItemQuantity } from '../features/cart/cartSlice.js';
import { calculateTotal } from '../features/utilities/utilities.js';
import { removeAll } from '../features/cart/cartSlice.js';
export default function Cart() {
const dispatch = useDispatch();
const cart = useSelector(state => state.cart); // * Note
const onInputChangeHandler = (name, input) => {
// If the user enters a bad value...
if (input === '') {
return;
}
// Otherwise, convert the input into a number and pass it along as the newQuantity.
const newQuantity = Number(input);
dispatch(changeItemQuantity(name, newQuantity));
};
const onRemoveAll = () => {
dispatch(removeAll());
};
// Use the cart and currencyFilter slices to render their data.
const cartElements = Object.entries(cart).map(createCartItem);
const total = calculateTotal(cart);
return (
<>
<Navbar/>
<div id="cart-container">
<ul id="cart-items">
{cartElements == '' ? <div className="cart-empty">Your cart is empty</div> : cartElements}
</ul>
<div className="price-btn-container">
{cartElements.length > 0 ? <button
onClick={() => onRemoveAll()}
className="remove-all"
>
Remove All
</button> : <div></div>}
<h3 className="total">
Total{' '}
<span className="total-value">
${total}
</span>
</h3>
</div>
</div>
<Footer/>
</>
);
function createCartItem([name, item]) {
if (item.quantity === 0) {
return;
}
return (
<li className="cart-list" key={name}>
<img src={item.img}/>
<p>{name}</p>
<p>{item.type}</p>
<div className="quantity-container">
<p>Quantity:</p>
<input
type="number"
className="item-quantity"
name="quantity"
value={item.quantity}
onChange={(e) => {
onInputChangeHandler(name, e.target.value);
}}/>
</div>
</li>
);
}
};
export const addItem = (itemToAdd) => {
return {
type: 'cart/addItem',
payload: itemToAdd,
};
};
export const removeAll = () => {
return {
type: 'cart/removeAll',
payload: initialCart
}
}
export const changeItemQuantity = (name, newQuantity ) => {
return {
type: 'cart/changeItemQuantity',
payload: {
name,
newQuantity
}
}
}
const initialCart = [];
export const cartReducer = (cart = initialCart, action) => {
switch (action.type) {
case 'cart/addItem': {
const { name, price, img, type } = action.payload;
// if the item already exists, increase the quantity by 1, otherwise set it to 1
const quantity = cart[name] ? cart[name].quantity + 1 : 1;
const newItem = { price, img, type, quantity };
// Add the new item to the cart (or replace it if it existed already)
return {
...cart,
[name]: newItem
};
}
case 'cart/removeItem':
return cart.filter(item => item !== action.payload);
case 'cart/removeAll':
return initialCart;
case 'cart/changeItemQuantity': {
const { name, newQuantity } = action.payload;
const itemToUpdate = cart[name];
const updatedItem = {
...itemToUpdate,
quantity: newQuantity
}
// Create a copy of itemToUpdate and update the quantity prop.
return {
...cart,
[name]: updatedItem
}
}
default: {
return cart;
}
}
};
Create two new actions/reducer cases to increment/decrement the item quantity.
export const addItem = (itemToAdd) => ({
type: 'cart/addItem',
payload: itemToAdd,
});
export const removeAll = () => ({
type: 'cart/removeAll',
payload: initialCart
});
export const changeItemQuantity = (name, newQuantity) => ({
type: 'cart/changeItemQuantity',
payload: { name, newQuantity }
});
export const const incrementItemQuantity = (name) => ({
type: 'cart/incrementItemQuantity',
payload: name
});
export const const decrementItemQuantity = (name) => ({
type: 'cart/decrementItemQuantity',
payload: name
});
const initialCart = {};
export const cartReducer = (cart = initialCart, action) => {
switch (action.type) {
case 'cart/addItem': {
const { name, price, img, type } = action.payload;
// if the item already exists, increase the quantity by 1, otherwise set it to 1
const quantity = cart[name] ? cart[name].quantity + 1 : 1;
const newItem = { price, img, type, quantity };
// Add the new item to the cart (or replace it if it existed already)
return {
...cart,
[name]: newItem
};
}
case 'cart/removeItem':
return cart.filter(item => item !== action.payload);
case 'cart/removeAll':
return initialCart;
case 'cart/changeItemQuantity': {
const { name, newQuantity } = action.payload;
return {
...cart,
[name]: {
...cart[name],
quantity: newQuantity
}
}
}
case 'cart/incrementItemQuantity':
const name = action.payload;
return {
...cart,
[name]: {
...cart[name],
quantity: cart[name].quantity + 1
}
}
case 'cart/decrementItemQuantity':
const name = action.payload;
return {
...cart,
[name]: {
...cart[name],
quantity: Math.max(1, cart[name].quantity - 1)
}
}
default:
return cart;
}
};
import React from 'react';
import "./Cart.css";
import Navbar from './Navbar.jsx';
import Footer from './Footer.jsx';
import { useDispatch, useSelector } from 'react-redux';
import {
changeItemQuantity,
decrementItemQuantity,
incrementItemQuantity,
removeAll
} from '../features/cart/cartSlice.js';
import { calculateTotal } from '../features/utilities/utilities.js';
export default function Cart() {
const dispatch = useDispatch();
const cart = useSelector(state => state.cart); // * Note
const onInputChangeHandler = (name, input) => {
// If the user enters a bad value...
if (input === '') {
return;
}
// Otherwise, convert the input into a number and pass it along as the newQuantity.
const newQuantity = Number(input);
dispatch(changeItemQuantity(name, newQuantity));
};
const onRemoveAll = () => dispatch(removeAll());
const incrementItem = (name) => dispatch(incrementItemQuantity(name));
const decrementItem = (name) => dispatch(decrementItemQuantity(name));
function createCartItem([name, item]) {
return (
<li className="cart-list" key={name}>
<img src={item.img}/>
<p>{name}</p>
<p>{item.type}</p>
<div className="quantity-container">
<p>Quantity:</p>
<input
type="number"
className="item-quantity"
name="quantity"
value={item.quantity}
onChange={(e) => {
onInputChangeHandler(name, e.target.value);
}}
/>
<button type="button" onClick={decrementItem.bind(null, name)}>
-
</button>
<button type="button" onClick={incrementItem.bind(null, name)}>
+
</button>
</div>
</li>
);
}
// Use the cart and currencyFilter slices to render their data.
const cartElements = Object.entries(cart)
.filter(([,item]) => item.quantity)
.map(createCartItem);
const total = calculateTotal(cart);
return (
<>
<Navbar />
<div id="cart-container">
{cartElements.length
? (
<ul id="cart-items">
{cartElements}
</ul>
) : <div className="cart-empty">Your cart is empty</div>
}
<div className="price-btn-container">
{!!cartElements.length && ( // <-- coerce length to boolean
<button
onClick={onRemoveAll}
className="remove-all"
>
Remove All
</button>
)}
<h3 className="total">
Total{' '}
<span className="total-value">
${total}
</span>
</h3>
</div>
</div>
<Footer />
</>
);
};
If you don't want the extra actions/reducers, you could handle it with the changeItemQuantity
action.
case 'cart/changeItemQuantity': {
const { name, newQuantity } = action.payload;
return {
...cart,
[name]: {
...cart[name],
quantity: Math.max(1, newQuantity),
}
}
}
import React from 'react';
import "./Cart.css";
import Navbar from './Navbar.jsx';
import Footer from './Footer.jsx';
import { useDispatch, useSelector } from 'react-redux';
import {
changeItemQuantity,
removeAll
} from '../features/cart/cartSlice.js';
import { calculateTotal } from '../features/utilities/utilities.js';
export default function Cart() {
const dispatch = useDispatch();
const cart = useSelector(state => state.cart); // * Note
const onInputChangeHandler = (name, input) => {
// If the user enters a bad value...
if (input === '') {
return;
}
// Otherwise, convert the input into a number and pass it along as the newQuantity.
const newQuantity = Number(input);
dispatch(changeItemQuantity(name, newQuantity));
};
const onRemoveAll = () => dispatch(removeAll());
const incrementItem = (name, item) => dispatch(
changeItemQuantity(name, item.quantity + 1)
);
const decrementItem = (name, item) => dispatch(
changeItemQuantity(name, item.quantity - 1)
);
function createCartItem([name, item]) {
return (
<li className="cart-list" key={name}>
<img src={item.img}/>
<p>{name}</p>
<p>{item.type}</p>
<div className="quantity-container">
<p>Quantity:</p>
<input
type="number"
className="item-quantity"
name="quantity"
value={item.quantity}
onChange={(e) => {
onInputChangeHandler(name, e.target.value);
}}
/>
<button type="button" onClick={decrementItem.bind(null, name, item)}>
-
</button>
<button type="button" onClick={incrementItem.bind(null, name, item)}>
+
</button>
</div>
</li>
);
}
// Use the cart and currencyFilter slices to render their data.
const cartElements = Object.entries(cart)
.filter(([,item]) => item.quantity)
.map(createCartItem);
const total = calculateTotal(cart);
return (
<>
<Navbar />
<div id="cart-container">
{cartElements.length
? (
<ul id="cart-items">
{cartElements}
</ul>
) : <div className="cart-empty">Your cart is empty</div>
}
<div className="price-btn-container">
{!!cartElements.length && (
<button
onClick={onRemoveAll}
className="remove-all"
>
Remove All
</button>
)}
<h3 className="total">
Total{' '}
<span className="total-value">
${total}
</span>
</h3>
</div>
</div>
<Footer />
</>
);
};