Transfer objects between two states in React - javascript

I'm creating a shopping cart with react hooks that looks like this
const ShoppingCart = () => {
const [products, setProducts] = useState([
{id: 1, name: 'Product 1', price: 2500},
{id: 2, name: 'Product 2', price: 2000},
{id: 3, name: 'Product 3', price: 2500},
])
const [cart, setCart] = useState()
const addToCart = (item) => (
setCart([cart, {item}])
// setCart([...cart, {item}])
// setCart([{item}])
)
return (
{
products.map((product, i) => (
<>
<Product name={product.name} price={product.price} key={product.id} />
<button
value='Add to cart'
// onClick={((product) => setCart(...cart, product))}
onClick={(product) => addToCart(product)}
// onClick={console.log(i)}
/>
{console.log(cart)}
</>
))
}
)
}
The lines commented out are some of the things I had tried that didn't work. Also tried it without the second arg[i] in the lamda function within map.
I'm trying to add a product to the cart object after a user clicks the button
The expected state of cart for example if the user clicks the button next to the product with id of one would be
[{id: 1, name:'Product 1', price: 2500}]
If it would be possible, I would also like to add another field to the object which would be amount

Set the initial value of the cart
const [cart, setCart] = useState([]); // [] is the initial value of the cart
remove product from the callback passed to onClick because it's not the product, it's the click event
<button value="Add to cart" onClick={() => addToCart(product)} />
and the spread operator should work fine
const addToCart = item => {
setCart([...cart, item]);
};
EDIT
if you want to add the item only once, check if it exists in the cart first :
const addToCart = item => {
const ndx = cart.findIndex(e => e.id === item.id);
if (ndx === -1) {
setCart([...cart, item]);
}
};

please use ...cart in your addToCart function because cart would be previous data array and when you do like that [...cart,item] then Previous array would merge in new array and will add new product object in that new array.
const addToCart = (item) => (
setCart([...cart,item])
)
and please don't pass param to your click callback function because you are getting this product from above not from click function .use anonymous function like this
onClick={() => addToCart(product)}

use onClick={((product) => setCart([...cart, product]))}

Related

Update object inside array in useState

I have this cart state in which the initial value is an empty array [].
const [cart,setCart] = useState([]);
This is how one of my product object looks like,
{id: 1, name: 'Shoe pair', price: 40}
There is an add to cart button for each product. So when the add to cart button is clicked addToCart function is triggered,
const addToCart = (item) => {
let initialItem = {id: item.id, name: item.name, quantity: 1}
let existingItem = cart.filter(cartItem => item.id === cartItem.id);
if(existingItem.length > 0){
existingItem.quantity = existingItem.quantity + 1;
} else {
setCart(crr => [...crr, initialItem ]);
}
}
What does addToCart do?
As you can see it is simple.First, it creates an object by setting the initial quantity to 1. If already the same product is present in the cart it updates the quantity in the cart product by 1 else the initialItem being added to the cart.
To monitor this occurrence I used useEffect hook,
useEffect(() => {
console.log(cart);
}, [cart]);
My problem is I can't see the cart in the console log when the quantity updates by 1 , But it shows when the initialItem is being pushed to the cart.
First issue: It is find, not filter.
Next issue - modifying item inside of array will not tell React that array is changed, you need to re-set state after existing item update also.
const addToCart = (item) => {
const initialItem = { id: item.id, name: item.name, quantity: 1 };
const existingItem = cart.find((cartItem) => item.id === cartItem.id);
if (existingItem) {
existingItem.quantity += 1;
setCart((curr) => [...curr]);
} else {
setCart((curr) => [...curr, initialItem]);
}
};
The reason your useEffect is not running when you think it should, is because its dependency is not being updated when you think it is. It will run when setCart is called and the reference to cart is updated, and then you will see your console log.
filter returns a new array -- will not mutate the original array.
docs -> https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
find returns the item by reference, if found -- otherwise returns undeifined.
docs -> https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find
Alternate example:
const addToCart = (item) => {
const existingItem = cart.find(i => i.id === item.id)
const updatedCart = existingItem
? cart.map(i => {
return i.id === item.id ? {...i, quantity: i.quantity + 1} : i
})
: [...cart, item]
}
setCart(updatedCart)
}

Reactjs/Redux - Objects are not valid as a React child

I am making a shopping cart - onClick I have redux adding items to cartItems array.
In the code below (increment reducer its the last one after add/remove) I am trying to get rid of duplicate values from the cartItems array which holds all the items added to the shopping cart, and display a total number of unique items in the cart with cartIcon: {value: 0} - which is by default 0 (before adding any items).
const initialState = {
cartItems: [],
cartQuantity: 0,
cartIcon: {value: 0},
}
export const addToCartSlice = createSlice({
name: 'cart',
initialState,
reducers: {
add(state, action ) {
const itemIndex = state.cartItems.findIndex(
(props) => props.id === action.payload.id
);
if(itemIndex >= 0){
state.cartItems[itemIndex].cartQuantity += 1;
} else {
const tempProduct = {...action.payload, cartQuantity: 1}
state.cartItems.push(tempProduct);
}
},
remove(state, action) {
const removeItem = state.cartItems.filter(
(cartItem) => cartItem.id !== action.payload.id
);
state.cartItems = removeItem;
},
increment: (state) => {
const Items = state.cartItems.filter(
(element, index) => state.cartItems.indexOf(element) === index);
state.value = Items.length;
} // if i just do state.value += 1
// then the value goes up by 1
// but I want to display the amount of unique entries
},
});
Here onClick I am pulling data from the item that was "added" to the cart and additionally trying to increment the cartIcon number by 1 (if the item hasn't been yet added to the array cartItems). The problem could be here? Because the error mentions all the props and data I'm pulling to be rendered into the cart.
const dispatch = useDispatch()
const handleAddToCart = (props) => {
dispatch(add(props));
};
return (<>
<div id={props.id} className='shopitem'>
<img src={props.url} />
<h2>{props.title}</h2>
<p className='boldprice'>${props.price}</p>
<button onClick={() => {
handleAddToCart(props);
dispatch(increment())
}}> ADD TO CART </button>
</div>
</>
)
}
And here I am trying to display the amount of unique items to the shopping cart icon.
const count = useSelector((state) => state.cart.cartIcon.value)
{count}
For some reason I am getting this error. If I just do state.value += 1 it will add +1 to the shopping cart icon, however I only want to display +1 for each unique item.
"Uncaught Error: Objects are not valid as a React child (found: object with keys {id, title, price, url, cartQuantity}). If you meant to render a collection of children, use an array instead."
Please help - I am relatively new to Javascript and programming overall.. I may be making a stupid mistake, so if something is clearly wrong.. then please let me know :)

REACT: How can I delete this without using unique id?

I want to delete this TODO with or without using a unique key
this is the HOOK code
const [todos, setTodos] = useState([{}])
const [user, setUser] = useState({
id: uuidv4(),
name: '',
email: '',
phone: '',
})
This one is the Function to set Input and delete a todo
const addTodo = (e) => {
e.preventDefault()
setTodos([...todos, user])
console.log(addTodo)
}
console.log(user)
const delTodo = (e, id) => {
e.preventDefault()
console.log(id)
todos.splice(id, 1)
setTodos([...todos])
}
Here These are being mapped
{todos.map((todo) => (
<div>
<li key={todo.id}>
{todo.name}, {todo.email}, {todo.phone}
</li>
<button onClick={delTodo} color='danger'>
Delete
</button>
</div>
))}
This is what i get when i console.log
link to image
Update your delTodo function as below.
splice uses first parameter as start index from array that you want to remove and second parameter is deleteCount. So in your case you need to get index of your object.
You can get index of object with indexOf(). It will return -1 of object does not belong to that array. So add if (index != -1) { } and then you can use todos.splice(index, 1); inside it.
const delTodo = (e, id) => {
e.preventDefault();
console.log(id);
let index = todos.indexOf(id);
if (index != -1) {
todos.splice(index, 1);
}
setTodos([...todos]);
}

Filtering items with Ant Design List and Radio Group

I would like to create a filter with Ant Design List and Radio Group. When the app is initially loaded I would like to display all items. When a radio button is selected I would like to display only the choices which correspond to its value. I have the following code:
const data = [
{
edges: [
{
node: {
name: "A One"
}
}
],
category: "A"
},
{
edges: [
{
node: {
name: "B One"
}
},
{
node: {
name: "B Two"
}
}
],
category: "B"
}
];
const ArticlesFilter = () => {
const options = data.map((item) => ({
label: item.category,
value: item.category
}));
const [value, setValue] = useState("");
const [articles, setArticles] = useState(data);
const filterArticles = (data) =>
data.filter((item) => item.category === value);
const onChange = (e) => {
setValue(e.target.value);
setArticles(filterArticles(data));
};
const renderItem = (data) =>
data.edges.map((item) => (
<List.Item>
<Card bordered={false} style={{ width: "100%" }}>
{item.node.name}
</Card>
</List.Item>
));
return (
<>
<Radio.Group
options={options}
onChange={onChange}
value={value}
optionType="button"
/>
<List dataSource={articles} renderItem={renderItem} />
</>
);
};
The problem is that when I select any option for the first time (A or B), no data is displayed at all. When I select any other option next, the data is displayed filtered by the previous value instead of the current value.
Here is the link to my codesandbox. I've added console log to my onChange() function and the render method, and they show different values as well. Where is my mistake? I would really appreciate your help.
Hey you need to do something slightly different, the onChange event runs very fast so when you're running the setArticles the value state has not changed, to accomplish what you want you can use the useEffect hook that will be listening when the value state changes(when you click a radio item for example) and you can set the articles inside that useEffect, so every-time you change the option, the articles will be filtered:
useEffect(() => {
if (value) setArticles(data.filter((item) => item.category === value));
}, [value])
const onChange = (e) => {
setValue(e.target.value);
};
Here's the forked sandbox in case you want to take a look:
https://codesandbox.io/s/antd-radio-group-filter-forked-vx3h6?file=/src/index.js
Also the if (value) condition inside the effect is to avoid filtering an empty string when the component is mounted, since that's your initial value for the value state.

I am not able to add user input to state properly, getting map not a function error when it is

I have a simple list which is stored in the App component. This is used to display all the people and I want to be able to add new people to this list. I am not able to add input into my state, I am getting an error that map is not a function. Am i not creating the array properly?
const App = () => {
const [persons, setPersons] = useState([
{
name: 'Artos Hellas',
id: 1
}
]);
const [newName, setNewName] = useState('');
const handleNewName = event => {
setNewName(event.target.value);
};
const addName = event => {
event.preventDefault();
const personObject = {
name: newName,
id: persons.length + 1
};
setPersons(persons.concat(personObject));
setPersons('');
};
const rows = () => persons.map(p => <li key={p.key}>{p.name}</li>);
return (
<div>
<h2>Phonebook</h2>
<form onSubmit={addName}>
<div>
name : <input value={newName} onChange={handleNewName} />
</div>
<div>
<button type="submit">add</button>
</div>
</form>
<h2>Numbers</h2>
<ul>{rows()}</ul>
</div>
);
};
Remove the setPersons(''); statement, you might wanted to use setNewName(''):
const addName = event => {
event.preventDefault();
const personObject = {
name: newName,
id: persons.length + 1
};
setPersons(persons.concat(personObject));
// setPersons('');
setNewName('');
};
Also, you got a wrong key prop while rendering list elements:
// v Not p.key
const rows = () => persons.map(p => <li key={p.id}>{p.name}</li>);
Moreover, it's confusing when you use a function named row and call it row(), you may try naming it as an action like renderRows or just use the ReactElement array:
const renderRows = () => persons.map(p => <li key={p.id}>{p.name}</li>);
<ul>{renderRows()}</ul>
// Or
const rows = persons.map(p => <li key={p.id}>{p.name}</li>);
<ul>{rows}</ul>

Categories

Resources