So ,I wanted to change this one product object in the UI with a button click, and reduce the quantity of the product. But the quantity amount did not change .What is the best move in situations like that.
let product={ a:1,b:2,c:3};
Lets say changing the value.
b:5;
please check this sample code
import React, { useState, useEffect } from 'react';
export default function App() {
const [products, setProducts] = useState([
{ name: 'Product 1', qty: 1, price: 10 },
{ name: 'Product 2', qty: 1, price: 20 },
]);
const [totalAmt, setTotalAmt] = useState(0);
useEffect(() => {
const total = products.reduce((s, i) => s += i.price * i.qty, 0);
setTotalAmt(total);
}, [products]);
const incrQty = (index) => {
setProducts((v) => {
const a = JSON.parse(JSON.stringify(v)); // FOR AVOIDING MUTATING DATA, BECAUSE IN STRICT MODE THIS SETSTATE CALLBACK WILL TRIGGER TWICE
a[index].qty += 1;
return [...a];
});
};
const decrQty = (index) => {
setProducts((v) => {
const a = JSON.parse(JSON.stringify(v));
a[index].qty -= 1;
return [...a];
});
};
return (
<div>
Total Amount: Rs.{totalAmt}
<ul>
{products.map((e, k) => (
<li key={k}>
{e.name}, qty={e.qty}, subtotal {e.qty * e.price},
<button onClick={e => incrQty(k)}>Incr Qty</button>
{
(e.qty > 1)?<button onClick={e => decrQty(k)}>Decr Qty</button>:null
}
</li>
))}
</ul>
</div>
);
}
Related
I was creating a shopping cart using react and redux and everything works perfectly without any errors I'm wondering how do I calculate the total price by adding up all the items in the cart
my cart-slice file
import { createSlice } from '#reduxjs/toolkit';
const cartSlice = createSlice({
name: 'cart',
initialState: {
items: [],
totalQuantity: 0,
},
reducers: {
addItemToCart(state, action) {
const newItem = action.payload;
const existingItem = state.items.find((item) => item.id === newItem.id);
state.totalQuantity++;
if (!existingItem) {
state.items.push({
id: newItem.id,
price: newItem.price,
quantity: 1,
totalPrice: newItem.price,
name: newItem.title,
});
} else {
existingItem.quantity++;
existingItem.totalPrice = existingItem.totalPrice + newItem.price;
}
},
removeItemFromCart(state, action) {
const id = action.payload;
const existingItem = state.items.find(item => item.id === id);
state.totalQuantity--;
if (existingItem.quantity === 1) {
state.items = state.items.filter(item => item.id !== id);
} else {
existingItem.quantity--;
existingItem.totalPrice = existingItem.totalPrice - existingItem.price;
}
},
},
});
export const cartActions = cartSlice.actions;
export default cartSlice;
the cart.js file
import { useSelector } from 'react-redux';
import Card from '../UI/Card';
import classes from './Cart.module.css';
import CartItem from './CartItem';
const Cart = (props) => {
const cartItems = useSelector((state) => state.cart.items);
return (
<Card className={classes.cart}>
<h2>Your Shopping Cart</h2>
<ul>
{cartItems.map((item) => (
<CartItem
key={item.id}
item={{
id: item.id,
title: item.name,
quantity: item.quantity,
total: item.totalPrice,
price: item.price,
}}
/>
))}
</ul>
</Card>
);
};
export default Cart;
the cart item file
import { useDispatch } from 'react-redux';
import classes from './CartItem.module.css';
import { cartActions } from '../../store/cart-slice';
import { useState } from 'react';
const CartItem = (props) => {
const dispatch = useDispatch();
const { title, quantity, total, price, id } = props.item;
const removeItemHandler = (e) => {
dispatch(cartActions.removeItemFromCart(id));
};
const addItemHandler = () => {
dispatch(
cartActions.addItemToCart({
id,
title,
price,
})
);
};
return (
<>
<li className={classes.item}>
<header>
<h3>{title}</h3>
<div className={classes.price}>
${total.toFixed(2)}{' '}
<span className={classes.itemprice}>(${price.toFixed(2)}/item)</span>
</div>
</header>
<div className={classes.details}>
<div className={classes.quantity}>
x <span>{quantity}</span>
</div>
<div className={classes.actions}>
<button onClick={removeItemHandler}>-</button>
<button onClick={addItemHandler}>+</button>
</div>
</div>
</li>
</>
);
};
export default CartItem;
can somebody please help me on how do we calculate the total price of the cart?
You can either do it in your reducers (addItemToCart, removeItemFromCart), or add another reducer with action that does that for you in a side effect.
Watching your implementation I see that you are mutating your state which is an anti-pattern. Redux is way safer to use and easier to manage with immutable state.
So instead of
state.totalQuantity++;
it is way better to return a new state object
{ ...state, totalQuantity: state.totalQuantity }
I don't know exactly whether redux/toolkit already copies the state internally, but you should not take that as given. Always return a new state.
Now to your Question (I dont know how your Item model looks like):
const totalPrice = state.items
.map(everyItem => {
return everyItem.quantity * everyItem.price;
})
.reduce((totalPrice, singleItemPrice) => totalPrice + singleItemPrice, 0);
Key takeaway here should be https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce
This would be a perfect use case for a Memoized Selector. Since you have state with a items array in it you can create a selector to reduces this state to a single number. For example:
const selectCart = state => state.cart
const selectTotalPrice = createSelector([selectCart], (cart) => {
return cart.items.reduce((total, item) => (item.price * quantity) + total, 0);
})
I'm stuck in project because my functional component do not re-render when I update this state.
const OneProduct = (props) => {
return (
<div className='productincart'>
<div className='name'>{props.name}</div>
<div className='price'>Price: {props.price}</div>
<div className='quantity'>{props.quantity}</div>
</div>)
}
const InsideCart = (props) => {
const cartTemporary = props.sendCart
const [cart, setCart] = useState([])
useEffect(() => {
setCart(cartTemporary)
}, [cartTemporary])
const showCart = () => {
console.log(cart)
}
return (
<div className='insidecart'>
{cart.map(product => <OneProduct name={product.name} price=
{product.price} quantity={product.quantity} key={uniqid()} />)}
<button onClick={showCart}>SHOW CART</button>
</div>
)
}
In code above, I putting products from shop to the cart, next I want to display it on the screen. When I'm putting new item to the cart (new object in cart array), everything works fine. When I'm only update quantity of object already exist in cart array, nothing happens. After quantity updating, cart is updated with new value, but it doesn't causing re-render of component.
That is problem to solve, how to make component re-render after quantity value update.
Below format of objects which I add to cart, also added mechanism of adding and increasing quantity.
const Products = (props) => {
const initialProducts = [
{ id: 0, name: "Fish - Betta splendens - Plakat Koi - Siamese fighting fish", image: image0, price: 23.17, quantity: 1 },
{ id: 1, name: "Fish - Betta splendens - Plakat - Siamese fighting fish", image: image1, price: 10.12, quantity: 1 },
{ id: 2, name: "Fish - Phenacogrammus interruptus - Congo tetra", image: image2, price: 5.19, quantity: 1 },
{ id: 3, name: "Fish - Thayeria boehlkei - Pinguin tetra", image: image3, price: 1.31, quantity: 1 },
{ id: 4, name: "Fish - Pterophyllum scalare - Angelfish", image: image4, price: 5.77, quantity: 1 },
{ id: 5, name: "Fish - Boeseman's rainbowfish Melanotaenia - Boesemani", image: image5, price: 4.90, quantity: 1 },
{ id: 6, name: "Fish - Ram cichlid - Mikrogeophagus ramirezi", image: image6, price: 4.61, quantity: 1 },
{ id: 7, name: "Fish - Corydoras aeneus sp.black venezuela", image: image7, price: 2.87, quantity: 1 },
]
const [products, setProducts] = useState(initialProducts)
const [toCart, setToCart] = useState([])
const getProductId = (e) => {
const idString = e.target.dataset.id
const id = Number(idString)
return id
}
const getProductById = (e) => {
const id = getProductId(e)
const itemFound = products.find(product => product.id === id)
const foundedOrNot = toCart.find(product => product === itemFound)
if (foundedOrNot === undefined) {
setToCart([...toCart, itemFound])
}
else {
const index = toCart.findIndex(product => product === itemFound)
toCart[index].quantity = toCart[index].quantity + 1
}
}
return (
<div className="products">
<Nav sendToCart={toCart} />
<InsideCart sendCart={toCart} />
<div className='container'>{products.map((product) => <Product onClick={getProductById} title={product.name} img={product.image} price={product.price} id={product.id} key={product.id} />)}</div>
</div>
);
};
Also add simple component which determines rendering what is inside the cart.
const OneProduct = (props) => {
return (<div className='productincart'><div className='name'>{props.name}</div> <div className='price'>Price: {props.price}</div><div className='quantity'>{props.quantity}</div></div>)
}
I already solved the problem. Issue was not using setToCart in product component. Thank you guys for all advices!
Updated code below:
const getProductById = (e) => {
const id = getProductId(e)
const itemFound = products.find(product => product.id === id)
const foundedOrNot = toCart.some(product => product === itemFound)
if (foundedOrNot === false) {
setToCart([...toCart, itemFound])
}
else {
const index = toCart.findIndex(product => product === itemFound)
const newCart = toCart.map(cart => {
if (cart.id === index) {
cart.quantity = cart.quantity + 1
return cart
}
return cart
})
setToCart(newCart)
}
}
I have an orders component that contains some orders each order has products and the user can update the quantity and price the problem is that the update process is very slow because if I update a product quantity for example all products in the order get remounted again and I think this is the main issue. if I have 100 products the product page render 100 times or more (one time for each product ) here is my current implementation on code sandbox: https://codesandbox.io/s/holy-tdd-3nj7g?file=/src/OrderingPage/Order/index.js
here is the order component that have multiple order but for simplicity lets assume we only have one order
import { useState, useCallback } from "react";
import Order from "./Order/index";
const OrderingScreen = () => {
const initialOrderData = {
order: {
total: 0,
vat: 0,
deliveryCharge: 0,
date: 0,
orderStart: 0,
orderEnd: 0,
customerGender: "",
actualPaid: 0,
dateStr: "",
payType: "cash",
itemsCount: 0,
orderDetails: [
{ name: "prod1", sellPrice: 120, quantity: 3 },
{ name: "prod2", sellPrice: 12, quantity: 2 },
{ name: "prod3", sellPrice: 1123, quantity: 2 },
{ name: "prod4", sellPrice: 1543, quantity: 1 },
{ name: "prod5", sellPrice: 123, quantity: 8 }
]
}
//other properties
};
const [ordersData, setOrdersData] = useState([initialOrderData]);
const resetOrder = useCallback(() => {
let ordersDataCopy = [...ordersData];
ordersDataCopy[0] = initialOrderData;
setOrdersData(ordersDataCopy);
}, [ordersData]);
const updateOrderProducts = useCallback(
(products) => {
let ordersCopy = [...ordersData];
ordersCopy[0]["order"]["orderDetails"] = [...products];
setOrdersData(ordersCopy);
},
[ordersData]
);
const updateOrder = useCallback(
(order) => {
let ordersCopy = [...ordersData];
ordersCopy[0]["order"] = { ...order };
setOrdersData(ordersCopy);
},
[ordersData]
);
return (
<Order
order={ordersData[0].order}
products={ordersData[0].order.orderDetails}
updateOrderProducts={updateOrderProducts}
updateOrder={updateOrder}
resetOrder={resetOrder}
/>
);
};
export default OrderingScreen;
here is the single order component
import OrderItem from "./OrderItem";
import { useEffect, memo, useCallback } from "react";
const Order = ({ order, products, updateOrderProducts, updateOrder }) => {
const handleOrderChange = useCallback((propertyName, value) => {
let orderCopy = { ...order };
orderCopy[propertyName] = value;
updateOrder(orderCopy);
});
const deleteProduct = useCallback((index) => {
let productsCopy = [...products];
productsCopy = productsCopy.filter(
(product) => product !== productsCopy[index]
);
updateOrderProducts(productsCopy);
}, []);
const handleOrderItemRemove = useCallback((index) => {
deleteProduct(index);
}, []);
const handleQuantityChange = useCallback((index, quantity) => {
let productsCopy = [...products];
productsCopy[index]["quantity"] = quantity;
updateOrderProducts(productsCopy);
}, []);
return (
<div className="d-flex px-2 flex-grow-1 mb-1">
{products.map((product, idx) => (
<OrderItem
product={product}
key={idx}
index={idx}
onRemove={handleOrderItemRemove}
onQuantityChange={handleQuantityChange}
updateProduct={handleOrderChange}
/>
))}
</div>
);
};
export default memo(Order);
and the last component which is the product component which I think is causing the performance issue (it render 1 + the number of products in the order if I update the quantity of one product )
import RemoveCircleIcon from "#mui/icons-material/RemoveCircle";
import AddCircleIcon from "#mui/icons-material/AddCircle";
import { memo, useMemo, useState, useEffect } from "react";
const OrderItem = ({ product, index, onQuantityChange }) => {
console.log("remount");
const [itemQuantity, setItemQuantity] = useState(product.quantity);
const incrementQuantity = () => {
onQuantityChange(index, itemQuantity + 1);
};
const decrementQuantity = () => {
itemQuantity > 1 && onQuantityChange(index, itemQuantity - 1);
};
useEffect(() => {
setItemQuantity(product.quantity);
}, [product.quantity]);
const productInfo = useMemo(() => (price, quantity, name) => {
let total = price * quantity;
total = +total.toFixed(2);
price = +price.toFixed(2);
return (
<div className={`col-9 col-xl-10 border rounded-start p-1 `}>
{name}
<div className="justify-content-around d-flex">
{"Price:" + price}
{" Quantity:" + quantity}
{" Total:" + total}
</div>
</div>
);
});
useEffect(() => {
setItemQuantity(product.quantity);
}, [product]);
const quantityColumn = (
<div>
<AddCircleIcon onClick={incrementQuantity} />
{itemQuantity}
<RemoveCircleIcon onClick={decrementQuantity} />
</div>
);
return (
<div style={{ marginBottom: "25px" }}>
{productInfo(product.sellPrice, product.quantity, product.name)}
{quantityColumn}
</div>
);
};
export default memo(OrderItem);
what I want to achieve is a snappy component update (maybe by making the product component mount only for the changed product)
you may see it fast on the sandbox but this version just explains the problem only... the real version is much complicated
You can improve performance by changing your React.memo components.
Instead of memo(OrderItem) pass as second argument function that will compare previous and current state:
function areEqualOrderItem(prevProps, nextProps) {
/*
return true if passing nextProps to render would return
the same result as passing prevProps to render,
otherwise return false
*/
return prevProps.quantity === nextProps.quantity;
}
export default memo(OrderItem, areEqualOrderItem);
Also I suggest do not use array index as component key try product name instead of this.
useCallback do nothing in your code. Instead you can use this one:
const handleOrderItemRemove = useCallback((index) => {
updateOrderProducts(prod => {
let productsCopy = [...prod];
productsCopy = productsCopy.filter(
(product) => product !== productsCopy[index]
);
return productsCopy;
});
}, [updateOrderProducts]);
const updateOrderProducts = useCallback(
(products) => {
setOrdersData(ords => {
let ordersCopy = [...ords];
ords[0]["order"]["orderDetails"] = [...products];
return ordersCopy;
});
},
[setOrdersData]
);
When you fix all your callbacks you can boost performance. At this time, your code cause rerender of all items almost every small change.
I'm trying to make a shopping app which repeats the same card. Instead of manually rendering them, I used map to render array objects like this:
Parent component:
const Home = () => {
const dummyData = [
{
id: 1,
title: tshirt,
price: 10
},
{
id: 2,
title: hat,
price: 20
}
]
const [totalPrice, setTotalPrice] = useState(0);
const itemNo = 2;
const handleClick = (price) => {
setTotalPrice(price * itemNo);
}
const RenderCards = () => {
return (
dummyData.map(
(d) =>
<Card key={d.id} title={d.title} price={d.price} totalPrice={totalPrice}/>
)
)
}
return(
<>
<RenderCards />
</>
)
}
and the child component:
const Card = (id, title, price, totalPrice) => {
return (
<>
<div key={id}>
<p>{title}</p>
<p>{totalPrice}</p>
<button onClick={() => handleClick(price)}>Click for total price</button>
</div>
</>
)
}
When clicking the button on each card, I'd like to display total price for each card, i.e. for card 1, total price should be 10 * 2 = 20, and for card 2 should be 40. Clicking card 1 should only change {totalPrice} of card 1, card 2 should not be affected, vice versa.
However what I have so far is when clicking the button, both card would show the same total price. I understand this behaviour as the same data is passed to the card component, but how can I individually set data for each card in this case when components are rendered from array map?
const Home = () => {
const dummyData = [
{
id: 1,
title: tshirt,
price: 10
},
{
id: 2,
title: hat,
price: 20
}
]
const [totalPrice, setTotalPrice] = useState([]); //<-- an empty array for start
const handleClick = (id, qty) => {
let newState = [...totalPrice]; //<--- copy the state
if (newState.find(item => item.id === id) != undefined) { // find the item to add qty, if not exists, add one
newState.find(item => item.id === id).qty += qty
} else {
newState.push({id:id, qty:qty});
}
setTotalPrice(newState); //<-- set the new state
}
const RenderCards = () => {
return (
dummyData.map(
(d) => {
const stateItem = totalPrice.find(item=> item.id === d.id); // return the item or undefined
const qty = stateItem ? stateItem.qty : 0 // retreive qty from state by id or 0 if the product is not in the array
return (
<Card key={d.id} title={d.title} price={d.price} totalPrice={d.price * qty}/> //calculate the total
)
}
)
)
}
return(
<>
<RenderCards />
</>
)
}
and card:
const Card = (id, title, price, totalPrice) => {
return (
<>
<div>
<p>{title}</p>
<p>{totalPrice}</p>
<button onClick={() => handleClick(id, 1)}>Click for add one</button> // add one, total price will be good on the next render
</div>
</>
)
}
maybe buggy but the idea is here
I have an array that I am trying to shuffle on initial load, and onClick. My functions seem to be working but aren't visible unless there is a page rerender.
2 issues I'm trying to resolve:
I want to shuffle on initial load, but the shuffle doesn't occur unless there is a rerender.
I want to shuffle on button press, but the shuffle doesn't occur unless there is a rerender.
Here is my CodeSandbox
Thanks
import React, {
useEffect,
useState
} from "react";
const myArray = [{
name: "cat",
count: 0
},
{
name: "dog",
count: 0
},
{
name: "hamster",
count: 0
},
{
name: "lizard",
count: 0
}
];
function shuffle(arra1) {
var ctr = arra1.length,
temp,
index;
while (ctr > 0) {
index = Math.floor(Math.random() * ctr);
ctr--;
temp = arra1[ctr];
arra1[ctr] = arra1[index];
arra1[index] = temp;
}
return arra1;
}
function App(props) {
const [list, setList] = useState(myArray);
useEffect(() => {
const mountArray = shuffle(myArray);
setList(mountArray);
}, []);
function handleShuffle() {
const changes = shuffle([...list]);
setList(changes);
console.log("Shuffle", myArray, changes);
}
return (
<div>
{list.map((x, index) => (
<div key = {x.name + x.index}>
{x.name} - {x.count}
<button onClick={() => setList([...list], (x.count = x.count + 1))}>
+
</button>
</div>
))}
<button onClick={handleShuffle}>
Shuffle
</button>
</div>
);
}
export default App;
HAI i have made some changes in App.js
import React, { useEffect, useState } from "react";
const myArray = [
{ name: "cat", count: 0 },
{ name: "dog", count: 0 },
{ name: "hamster", count: 0 },
{ name: "lizard", count: 0 }
];
function shuffle(arra1) {
var ctr = arra1.length,
temp,
index;
while (ctr > 0) {
index = Math.floor(Math.random() * ctr);
ctr--;
temp = arra1[ctr];
arra1[ctr] = arra1[index];
arra1[index] = temp;
}
return arra1;
}
function App(props) {
const [list, setList] = useState(myArray);
useEffect(() => {
const mountArray = shuffle(myArray);
setList(mountArray);
}, []);
function handleShuffle() {
const changes = shuffle([...list]);
setList(changes);
console.log("Shuffle", myArray);
}
return (
<div>
{list.map((x, index) => (
<div key={x.name + x.index}>
{x.name} - {x.count}
<button onClick={() => setList([...list], (x.count = x.count + 1))}>
+
</button>
</div>
))}
<button onClick={handleShuffle}>Shuffle</button>
</div>
);
}
export default App;
Your setList function is used to modify the array and returns a new array, with shuffled values, so you need to apply that function non initial rendering.
useEffect(() => {
setList(shuffle(myArray))
}, []);
Html changes only if the state has changed, so make some state inside a component, and update it every time you want to update the html.
function App(props){
const [myArray, setMyArray] = useState([])
// rest of the code
}