Updating useState without inserting another object - javascript

This is the full code of the component, I am so tired that I can't think much on how to solve this problem
import React, { useEffect, useState } from 'react';
import { Link } from 'react-router-dom';
function CnContent(props) {
const getCart = JSON.parse(localStorage.getItem('cart'));
const products = [
{
id: 0,
imgsrc: process.env.PUBLIC_URL + '/assets/images/products/grocery-staples/pro_1.jpg',
name: "O'range 500 ml Coconut Oil(Bottle)",
quantity: "500 ml",
offer_price: "₹ 116",
real_price: "₹ 219",
inCart: 1
},
{
id: 1,
imgsrc: process.env.PUBLIC_URL + '/assets/images/products/grocery-staples/pro_2.jpg',
name: "Parachute 100% Pure Coconut Oil (Plastic Bottle)",
quantity: "600 ml",
offer_price: "₹ 210",
real_price: "₹ 250",
inCart: 1
},
{
id: 2,
imgsrc: process.env.PUBLIC_URL + '/assets/images/products/grocery-staples/pro_3.jpg',
name: "Fortune Soya Chunks",
quantity: "1 kg",
offer_price: "₹ 126",
real_price: "₹ 135",
inCart: 1
},
]
const [cart, setCart] = useState(getCart);
useEffect(() => {
localStorage.setItem('cart', JSON.stringify(cart));
}, [cart])
function addToCart(pr) {
let duplicate = cart.find((x) => x.id === pr.id)
if (!duplicate) {
setCart((prevState) => {
return [...prevState, pr]
});
}
else {
setCart((prevState) => [...prevState, prevState[pr.id] = { inCart: prevState[pr.id].inCart + 1 }]); // This creates another object and sends to the array (cart), but I want to change the value of the specific product(cart[pr.id]).
}
}
return (
<>
<div className='cn-table'>
<div className='section-left'></div>
<div className='section-right'>
{
products.map((pr, i) => {
return (
<div tabIndex='-1' className='products' key={i}>
<div className="products__wrapper">
<Link to='https://www.youtube.com' className='products__link'>
<div className='products__img'>
<img src={pr.imgsrc} alt={pr.name}></img>
</div>
<div className='products__name'>{pr.name}</div>
<div className='products__quantity'>{pr.quantity}</div>
</Link>
<div className='products__details'>
<div className='products__details__price'>
<div className='products__details__price__offer'>{pr.offer_price}</div>
<div className='products__details__price__real'>{pr.real_price}</div>
</div>
<div className='add-to-cart zero-products'>
<button className='add-to-cart__remove' >-</button>
<button className='add-to-cart__text active'>{pr.inCart}</button>
<button className='add-to-cart__add' onClick={() => addToCart(pr)}>+</button>
</div>
</div>
</div>
</div>
)
})
}
</div>
</div>
</>
)
}
export { CnContent };
These are results of localStorage value, the problem is instead of changing the value of inCart of the specific object, it's creating a new object with property "inCart".
0: {id: 0, pr_category: "grocery-staples", imgsrc: "/assets/images/products/grocery-staples/pro_1.jpg",…}
1: {id: 1, pr_category: "grocery-staples", imgsrc: "/assets/images/products/grocery-staples/pro_2.jpg",…}
2: {inCart: 1} /*Instead of this new object I want to change the value in the upper objects*/
Any suggestions will be appreciated. Thank you !
EDIT: Changed the value of inCart from 0 to 1.

You need to map over the cart and update only the product you want
setCart((prevState) => prevState.map(product=>{
if (product.id === pr.id) {
return {
...product,
inCart: product.inCart + 1
}
}
return product;
});

The problem is solved this code if I change the value of inCart from 0 to 1, but if possible post an answer where I don't have change the value of inCart.
function addToCart(pr) {
setCart((prevState) => {
let duplicate = prevState.find((x) => x.id === pr.id)
if (!duplicate)
return [...prevState, pr];
else {
prevState[pr.id].inCart += 1;
return [...prevState];
}
})
}
I have use this code above instead of:
function addToCart(pr) {
let duplicate = cart.find((x) => x.id === pr.id)
if (!duplicate) {
setCart((prevState) => {
return [...prevState, pr]
});
}
else {
setCart((prevState) => [...prevState, prevState[pr.id] = { inCart: prevState[pr.id].inCart + 1 }]); // This creates another object and sends to the array (cart), but I want to change the value of the specific product(cart[pr.id]).
}
}

Related

Why my functional component doesn't re-render when change state? (react)

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)
}
}

Set acitve classname to multiple items in react js (map) and remove

I need to set the active classname to multiple onclick items inside a .map
I need the list of active items that were clicked
The items that were clicked will be highlighted in yellow, and when i click the same item again it should be removed from active list items.
const [data, setData] = useState([]);
const [activeIndicies, setActiveIndicies] = useState(() =>
data?.map(() => false)
);
useEffect(() => {
// This data is coming from the API response
const data = [
{ id: 1, name: "one" },
{ id: 2, name: "two" },
{ id: 3, name: "three" }
];
setData(data);
}, []);
return statement
onClick={() => {
setActiveIndicies(
activeIndicies.map((bool, j) => (j === index ? true : bool))
);
}}
Code Sandbox
Thank you.
try this one:
import "./styles.css";
import React, { useState, useEffect } from "react";
export default function App() {
const [data, setData] = useState([
{ id: 1, name: "one", active: false },
{ id: 2, name: "two", active: false },
{ id: 3, name: "three", active: false }
]);
return (
<div className="App">
<h2>Set active className to multiple items on .map</h2>
{data?.map((item, index) => {
return (
<p className={data[index].active ? "selected" : "notselected"}
onClick={() => {
setData((prevState) =>
_.orderBy(
[
...prevState.filter((row) => row.id !== item.id),
{ ...item, active: !item.active }
],
["name"],
["asc"]
)
);
}}
>
{item.name}
</p>
);
})}
</div>
);
}
You can acheive this by simply making some minor changes to your code:
// Changing the state value to an object so that it can
// store the active value for exact item ids
const [activeIndicies, setActiveIndicies] = useState({});
Then inside of .map()
....
// Checking if there is any value for the item id which is being mapped right now.
const selected = activeIndicies[item.id];
return (
<p
className={selected ? "selected" : "notselected"}
onClick={() => {
/* Then setting the state like below where it toggles
the value for particular item id. This way if item is
selected it will be deselected and vice-versa.
*/
setActiveIndicies((prevState) => {
const newStateValue = !prevState[item.id];
return { ...prevState, [item.id]: newStateValue };
});
}}
// Key is important :)
key={item.id}
>
{item.name}
</p>
);
Hello, friends!
I solved this problem in a more convenient way for me )
const data = [
{ id: 1, name: "Ann", selected: true },
{ id: 2, name: "Serg", selected: false },
{ id: 3, name: "Boris", selected: true },
];
//you don't even need to have a boolean field in the object -
//it will be added by itself on the first click on the element
// const data = [{ name:"Ann", id:1}, { name:"Serg", id:2 },{ name:"Boris", id:3 },]
const [users, setUsers] = useState(data); // no square brackets-[data]
function handleActive(item) {
setUsers((prev) => {
return prev.map((itemName) => {
if (itemName.name === item.name) {
return { ...itemName, selected: !itemName.selected };
// itemName.selected = !itemName.selected // or so
}
return itemName;
});
});
}
return (
{
users.map((item, i) => {
// let current = item.selected === true
// or so -> className={current === true ? 'users': ''}
return (
<div
onClick={() => handleActive(item)}
className={item.selected === true ? "active" : ""}
key={i}
>
{item.name}
</div>
);
})
}
);

Change a single product quantity using state (react)

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>
);
}

Why setState() isn't working in React in my case?

I'm trying to implement a shopping cart by using contexts. The context works fine, but I need to create a method that removes one product each click. To do that I have to use useState() function. However, it doesn't even change the data in the state.
Here is my code for the function. Let me know if something isn't clear
For more info. productsToPurchase is an array that have all the purchased products which looks like that:
[{itemId: "ps-5", qty:2},{itemId: "iphone-xr", qty:4},{itemId:"ps-4", qty:7}]
export const CartContext = createContext()
class CartContextProvider extends Component {
state = {
productsToPurchase: [],
totalPrice:[]
}
addProduct = (itemId, prices) => {
this.setState(oldState => {
const objWithIdExist = oldState.productsToPurchase.find((o) => o.itemId === itemId);
return {
productsToPurchase: !objWithIdExist
? [...oldState.productsToPurchase, { itemId, qty: 1, price: prices }]
: oldState.productsToPurchase.map((o) =>
o.itemId !== itemId ? o : { ...o, qty: o.qty + 1 }
)
}
})
this.setState(oldState=>{
return{
totalPrice: getTotalAmount(oldState.productsToPurchase)
}
})
}
decreaseProduct = (itemId) =>{
this.setState(oldState=>{
// catch the item by mapping
return oldState.productsToPurchase.map(product=>{
if (product.itemId === itemId){
if (product.qty === 1){
//remove the product from the list
console.log("qty is 1!")
}
else {
console.log("Quantity is more than 1")
return {...product, qty: product.qty - 1}
}
}
})
})
}
render() {
return (
<CartContext.Provider value={{...this.state, addProduct: this.addProduct, decreaseProduct: this.decreaseProduct}}>
{this.props.children}
</CartContext.Provider>
)
}
}
function getTotalAmount(productsToPurchase) {
const totalPrice = []
productsToPurchase.map((product, productIndex) =>{
product.price.map((price, priceIndex)=>{
if (productIndex === 0){
totalPrice.push({currency: price.currency, amount: price.amount * product.qty})
}
else {
totalPrice.map(total=>{
if (total.currency === price.currency){
total.amount = total.amount + (price.amount * product.qty)
}
})
}
})
})
return totalPrice
}
export default CartContextProvider;
If you want to subtract 1 from the selected product quantity and remove it if it hits 0, try this
// Make sure you return the new state object in its entirety
this.setState(({ productsToPurchase, ...oldState }) => ({
...oldState,
productsToPurchase: productsToPurchase.reduce((arr, product) => {
// is this the selected product?
if (product.itemId === itemId) {
// product qty > 1, include it with decrement
if (product.qty > 1) {
return [...arr, {
...product,
qty: product.qty - 1
}]
}
return arr // otherwise exclude this product
}
// otherwise, just include this product
return [...arr, product]
}, [])
}))

How can you check if there is the same item in an array and do something otherwise?

I'm trying to push items to an array but I want that if there's a similar item in an array, to just update the count to 1 and not add the item another time on the array. I've tried plenty of things such as the include() function but it is not working as expected, because when I put item.includes(product) as I'm mapping through each product, whenever I add an item, the count gets updated for each product... For example when I add one product, for one item, another item also gets that update...
import React, {useState, useContext} from 'react'
import data from './data.js'
import useCountsContext from './context/useCountsContext.js'
var uniqid = require('uniqid');
function Shop({ data }) {
const {count, setCount} = useContext(useCountsContext)
const {item, setItem} = useContext(useCountsContext)
const addCart = (productsId) => {
setCount(count + 1)
data.forEach((product) => {
if (item.includes(product)) {
product.count += 1
} else if (product.id === productsId) {
setItem(item.concat(product))
}
})
}
console.log(item)
return (
<div>
<h1>Shop</h1>
<div className="div___shop">
{data.map(({id, img, button}) => (
<>
<img className="img___shop" key={id} src={img}></img>
<div key={id}>
<button onClick={() => addCart(id)}>{button}</button>
</div>
</>
))}
</div>
</div>
)
}
export default Shop
my data file:
import diCaprio from './img/diCaprio.jpg'
import steveJobs from './img/steveJobs.jpg'
import lips from './img/lips.jpg'
import buda from './img/buda.jpg'
import spaceDog from './img/spaceDog.jpg'
import astroNube from './img/astroNube.jpg'
import banksy from './img/Banksy.jpg'
import banksyDJ from './img/banksyDJ.jpg'
var uniqid = require('uniqid');
const data = [{
id: uniqid(),
title: "Steve Jobs",
img: steveJobs,
homeImg: steveJobs,
button: "add to cart",
count: 0
},
{
id: uniqid(),
img: diCaprio,
homeImg: diCaprio,
button: "add to cart",
count: 0
},
{
id: uniqid(),
img: lips,
homeImg: lips,
button: "add to cart",
count: 0
},
{
id: uniqid(),
img: buda,
homeImg: buda,
button: "add to cart",
count: 0
},
{
id: uniqid(),
img: spaceDog,
button: "add to cart",
count: 0
},
{
id: uniqid(),
img:astroNube,
button: "add to cart",
count: 0
},
{
id: uniqid(),
img: banksy,
button: "add to cart",
count: 0
},
{
id: uniqid(),
img:banksyDJ,
button: "add to cart",
count: 0
}
]
export default data;
item is a list (array) of products and product is an object.
you cannot use includes on an array which contains objects, you can use a general for loop, forEach, or modern find to search for product in your items array
const addCart = (productsId) => {
setCount(count + 1)
data.forEach((product) => {
const exists = item.findIndex((i) => i.id === product.id) > -1;
// if check is on title
// const exists = item.findIndex((i) => i.title === product.title) > -1;
if (exists) {
product.count += 1
} else if (product.id === productsId) {
setItem(item.concat(product))
}
})
}
Update after OP's comment
import React, {useState, useContext} from 'react'
import data from './data.js'
import useCountsContext from './context/useCountsContext.js'
var uniqid = require('uniqid');
function Shop({ data }) {
const {count, setCount} = useContext(useCountsContext)
const {item, setItem} = useContext(useCountsContext)
const addCart = (productsId) => {
setCount(count + 1)
// grab the product object from data array
const product = data.find((d) => d.id === productsId);
// if product is not found
if (product == null) {
throw new Error("Invalid ProductId");
}
// check if product already exists in item list
// grab the index of the product in the item list
const exists = item.findIndex((i) => i.id === product.id);
// if index is -1, the product doesn't exists
if (exists === -1) {
// add to item array
setItem(item.concat(product));
} else {
// product already exists
// increase the count
const cart = [...item]; // create a Shallow copy of cart
cart[exists].count += 1; // increment the count
setItem(cart); // update the cart state
}
}
return (
<div>
<h1>Shop</h1>
<div className="div___shop">
{data.map(({id, img, button}) => (
<>
<img className="img___shop" key={id} src={img}></img>
<div key={id}>
<button onClick={() => addCart(id)}>{button}</button>
</div>
</>
))}
</div>
</div>
)
}
export default Shop

Categories