Is there a way to pass a useState array to another component? - javascript

I'm creating a video game shopping cart.
I have a component that is storing data of recent video games.
VideoGameList.js
const VideoGameList = [
{
id: 1,
title: "Fire Emblem Engage",
inCart: false,
},
];
And another component that displays the objects in their own div. Inside that component I have this code which takes the VideoGameList array and sets a new array upon clicking "Add to Cart" and changes the inCart value from false to true.
VideoGame.js
const [cart, setCart] = useState(VideoGameList);
const handleCartStatus = (id) =>
setCart(
cart.map((value) =>
value.id !== id ? value : { ...value, inCart: !value.inCart }
)
);
I then have to map through the new array like so
{cart.map(
({ id, title, inCart }, index) => (
<div>
<img key={src} src={src} alt={title} />
<div>
<p>{title}</p>
<p>{releaseDate}</p>
</div>
<div>
<p>${price}</p>
<button
onClick={() => handleCartStatus(id)}
>
Add to Cart
</button>
Here's my problem
I have a third component which is the actual ShoppingCart page. Originally when I made this component I was using this code
ShoppingCart.js
const ShoppingCart = () => {
return (
<>
<Header />
{VideoGameList.some((v) => v.inCart) ? (
<div>
{VideoGameList.filter((v) => v.inCart).map(
(
{ id, title, inCart },
index
) => (
<div>
<img key={src} src={src} alt={title} />
<div>
<p>{title}</p>
<p>{releaseDate}</p>
</div>
<div>
<p>${price}</p>
<button>Add to Cart</button>
</div>
</div>
)
)}
</div>
) : (
<div>
<h1>Your Shopping Cart Is Empty!</h1>;
<div className="shopping-cart-image">
</div>
</div>
)}
</>
);
};
The code works in one of two ways. Either the user has an empty shopping cart and an image + some text is displayed or they have a minimum of one item in their shopping cart and a div of said item (video game) was displayed. An item is considered in their shopping cart when the inCart value is set to true. I was basing the some and filter methods on the VideoGameList array and was manually changing the inCart values to true to see if it worked (it did) however, because I have to change state from the VideoGame component I have to change VideoGameList.some and VideoGameList.filter to cart.some and cart.filter to get the updated inCart values but every time I change it I get this error.
src\Components\ShoppingCart.js
Line 11:8: 'cart' is not defined no-undef
Line 13:12: 'cart' is not defined no-undef
If I continue using VideoGameList it will never update the Shopping Cart but I can't find a way to use cart from my VideoGame component inside my ShoppingCart component. Please advice.

Related

Setting props is duplicating components on screen (React)

Working on a shopping cart project that has two main pages: The video game page that shows a bunch of video games (stored as objects inside an array and mapped to the screen) and a shopping cart page that displays a list of any game with an inCart value set to true. The default value of every game is false. In order to set the value to true the user simply clicks "Add to Cart" from the video game page.
I've spent the past several hours trying to figure out why my code isn't working properly.
VideoGameList is the name of my array that is holding the object data.
const VideoGameList = [
{
id: 1,
title: "Fire Emblem Engage",
inCart: false,
},
];
VideoGame.js
// VideoGameList is the name of array holding video game values
const [cart, setCart] = useState(VideoGameList);
// Changes Boolean values from false to true
const handleCartStatus = (id) =>
setCart(
cart.map((value) =>
value.id !== id ? value : { ...value, inCart: !value.inCart }
)
);
I'm mapping the data to display on screen which shows each game in its own nice div and included a button and when the user clicks the button it calls the handleCartStatus function to change the Boolean value of inCart from false to true. However, in order to display the most recent values, I can't use VideoGameList.map I have to use cart.map as that is what state is changing each time I click "Add to Cart".
<ShoppingCart cart={cart} /> // sends props of cart to ShoppingCart component
{cart.map(({ id, title }, index) => (
<div>
<img key={src} src={src} alt={title} />
<div>
<p>{title}</p>
</div>
<div>
<button
onClick={() => handleCartStatus(id)}
>
Add to Cart
</button>
</div>
</div>
))}
Because I need access to cart.some and cart.filter in another component I'm sending the props to my third component which is the actual ShoppingCart page. When I was using VideoGameList.map it was only showing the unchanged values so nothing was ever displayed on screen.
This page displays a div of every object that has its inCart value set to true.
ShoppingCart.js
const ShoppingCart = (props) => {
return (
<>
{props.cart?.some((v) => v.inCart) ? (
<div>
{props.cart
?.filter((v) => v.inCart)
?.map(({ title }, index) => (
<div>
</div>
))}
</div>
Here's where I need help
I know using ShoppingCart cart={cart} /> inside my VideoGame component is causing the ShoppingCart to display in my VideoGame component but how else am I supposed to send the props of cart? I should also mention I have <Header /> displayed in each component and it's now showing up twice on my VideoGame page, one from my VideoGame component and the other from my ShoppingCart component. Furthermore, I don't understand why using props.cart?.some inside of my ShoppingCart component isn't working at all and is showing a blank screen. It's essentially giving me unidentified values despite working in my VideoGame page.

How to use one function multiple times in reactjs?

I'm trying to create quantity section with reactjs but this is working only for one section. How can I make it multiple.
When I'm click on up arrow then count is increasing same in all the {qty}. but I need when I'll click on up arrow then it should be count as per the products.
Problem:- count is coming same:
Solution:- count should not same it should be like given below:
My Code:-
const BuyProducts = () => {
const title = 'Buy Products New';
const classes = productsStyles();
const [qty, setQty] = useState(0);
const quantityMinus = () => {
if (!qty == 0) {
setQty(qty - 1);
}
};
const quantityPlus = () => {
setQty(qty + 1);
};
return (
<div>
<div>
<div onClick={quantityPlus} className="qty-action">
<FontAwesomeIcon icon={faAngleUp} />
</div>
<div>{qty}</div>
<div onClick={quantityMinus} className="qty-action">
<FontAwesomeIcon icon={faAngleDown} />
</div>
</div>
<div>
<div onClick={quantityPlus} className="qty-action">
<FontAwesomeIcon icon={faAngleUp} />
</div>
<div>{qty}</div>
<div onClick={quantityMinus} className="qty-action">
<FontAwesomeIcon icon={faAngleDown} />
</div>
</div>
</div>
);
};
Thanks for your efforts!
You'll want to create some form of a data structure to manage multiple quantities. Since you're only using one qty variable right now, only one thing will ever change.
Let's say you have a simple CartItem interface to represent some shopping cart item that has quantity + a name or id of the item being purchased.
(It could look something like this if you were using typescript.)
interface CartItem {
name: string;
quantity: number;
}
Then in React, you'd want to store a collection of these CartItems in your state somewhere so you can track how many objects you need to render. You could use an object, set, or just a simple array depending on your use case. Let's use an array.
const [cartItems, setCartItems] = useState([{name: 'apple', quantity: 0}, {name: 'orange', quantity: 1}]); // initial data as an example
Next, your quantityUp/down functions need to be a little bit more generic so that they can receive a CartItem object as an argument and you can accordingly use that to figure out which cart item to update.
const changeQuantity = useCallback((item, quantityChange) => {
// Find the item to update
const oldCartItems = [...cartItems];
const itemIndex = oldCartItems.findIndex((oldItem) => oldItem.name === item.name);
// Change the quantity
oldCartItems[itemIndex].quantity += quantityChange;
// Put it back in state
setCartItems(oldCartItems);
}, [cartItems]);
Finally, in your rendering/JSX, you don't need to keep writing the same JSX over and over again. You can map through the cartItems state.
return (
<div>
{cartItems.map((cartItem) =>
<div>
<div onClick={() => changeQuantity(cartItem, 1)} className="qty-action">
<FontAwesomeIcon icon={faAngleUp} />
</div>
<div>{cartItem.quantity}</div>
<div onClick={() => changeQuantity(cartItem, -1)} className="qty-action">
<FontAwesomeIcon icon={faAngleDown} />
</div>
</div>
}
</div>
);

Add item to cart (React-router / Redux)

I'm trying to complete my first big assignment for school where I need to use react-router and redux. I'm at the last page/component but have been completely stuck for the past 2 days. The app is an app to order coffee. I have a menu page/component, to which I added my menu items with a .map of a json file for an 'items' component. I now need to be able to add my items onClick to the cart component but I can't figure out how.
I had thought of maybe saving either the whole needed data of the item to an empty array, which then I would push to my cart component and then map all the info for a new component to generate the cart items. But after 2 days of failed attempts, I just now managed within the 'items' component to create a 'inCart' state that saves an array with the 2 pieces of info I need for my cart.
There still are 2 issues though:
on the first click nothing gets saved, but its only after the second click that the items show up in my array.
because the state is created within the 'items' component which then is mapped, my state is also connected to only one item at a time, so if I click on 2 different types of coffee, these won't end up in the same array.
I get the feeling that maybe redux would be the way to go in this case, but as I'm very new at it, I can't quite wrap my head around how that would be implemented in this case.
Overall what I find is making it hard for me to find a solution is the fact that the button I need my onClick function to work with is inside the 'items' component while I would need a state with my array to be in my 'menu' component, in order to record all different types of coffee chosen. That's why I'm thinking Redux would maybe be the solution, but not sure how to implement it.
Any types of suggestions that would put me in the right direction to get this problem solved are warmly welcome!!
Following are my 'menu' component and 'item' component code to clarify where I'm at right now.
MENU COMPONENT:
const Menu = () => {
let items = require('../assets/menu.json');
let counter = useSelector(state => state.cartCounter);
const dispatch = useDispatch();
const showNav = useSelector(state => state.toggleNav);
const showCart = useSelector(state => state.toggleCart);
if (showNav){
return ( <Nav /> )
} else {
return (
<section className='menu'>
{showCart ? <Cart /> : ''}
<header>
<section className='nav' onClick={() => dispatch(toggleNav())}>
<div ></div>
<div></div>
<div></div>
</section>
<section onClick={() => dispatch(toggleCart())}>
<img src={bag} alt='bag' className='bag'></img>
<p className='cart-counter'>{counter}</p>
</section>
</header>
<h1>Meny</h1>
{items.map((item, index) => <Item key={index} data={item}/>)}
</section>
)}
}
export default Menu;
ITEM COMPONENT:
const Item = (props) => {
const dispatch = useDispatch();
let [inCart, setInCart] = useState([]);
const updateInCart= (e) => {
setInCart([
...inCart,
{
title: props.data.title,
price: props.data.price,
}
]);
};
const onClick = (e) => {
dispatch(cartIncrement());
updateInCart();
console.log(inCart)
}
return (
<section className='item-container'>
<img src={add} alt='add' className='add-cart' onClick={onClick}></img>
<article className='item-text'>
<section className='first-line'>
<h2 >{props.data.title}</h2>
<h2 className='price'>{props.data.price} kr</h2>
</section>
<p className='beans'>{props.data.beans}</p>
</article>
</section>
);
}
export default Item;

How to add an object to an array using data from user input?

I am building a recipe app that allows a user to add custom recipes and then display them.
I am able to add one recipe just fine, but when I add a second recipe, it changes the first one to have the same data as the second.
The initial state of the list of recipes is set as an empty array:
recipeList: [],
newRecipe: {
title: '',
source: '',
id: ''
Every time I hit a submit Recipe button, I want to be able to add a new recipe to the array and display it as a thumbnail (see screenshot above). The new recipe is in the form of an object, and as seen above, the initial state of the new recipe object is set to empty. So essentially I want to be able to put new user input into the new recipe object, add it to the empty array, and display the entire array as recipe thumbnails.
In the React Dev Tools, I see both recipes in the array, so I know they are being added. But for some reason they are not being displayed correctly.
Upon submission, each item in the array is mapped over and returned as a thumbnail:
return (
<div className="App">
<Navbar />
<div className='app-body'>
{this.state.recipeList.map((recipe) => (
<RecipeCardThumbnail
key={this.state.id}
title={this.state.title}
source={this.state.source}
/>
))}
So it could be a problem with the map() method as it seems it is not mapping through the correct array.
This is the function that fires when I submit a form to add a new recipe:
handleSubmitNewRecipe = e => {
e.preventDefault();
this.handleAddNewRecipe(this.state.title, this.state.source, this.state.id);
};
And here's the function for adding a new recipe:
handleAddNewRecipe = (title, source) => {
const newRecipe = {
title: title,
source: source,
id: (Math.floor(Math.random()* 1000))
}
this.setState({recipeList: [...this.state.recipeList, newRecipe] });
console.log(this.state.recipeList)
};
When the array is mapped over, it returns a component called<RecipeCardThumbnail />. Here is the code for that component:
return (
<div className="card select thumbnail-card">
<div className="card-body">
<h5 className="card-title">{this.props.title}</h5>
<h6 className='card-subtitle text-muted'>{this.props.source}</h6>
<ul className="qty-details">
<li >{this.props.servings}</li>
<li >{this.props.prepTime}</li>
<li >{this.props.cookTime}</li>
<li >{this.props.totalTime}</li>
</ul>
</div>
</div>
);
In addition to each item not being displayed, I also get an error saying that each child needs a unique key. I thought that the id that I have works as a key.
Not sure if this is enough code, but you can view the entire code here: Recipe App on Github
At a glance it seems you should be using properties of recipe here, not this.state.
So this:
{this.state.recipeList.map((recipe) => (
<RecipeCardThumbnail
key={recipe.id}
title={recipe.title}
source={recipe.source}
/>
))}
Instead of this:
{this.state.recipeList.map((recipe) => (
<RecipeCardThumbnail
key={this.state.id}
title={this.state.title}
source={this.state.source}
/>
))}

Controlled Input onChange Event Fires Only Once - React

Only one key press is registered, then the input loses focus however I can click back into the component and add ... one character. State is updated.
Since the child components are state-less I assumed that just passing the handlers down as props as described in the docs would be sufficient, but everything from changing the binding for the method to adding a constructor have yielded the same results. I can usually find an answer on the site but no luck this time.
const list = [
{
id : 0,
title : "Went well",
showCard : false,
cards : [],
color : "pink"
},
{
id : 1,
title : "To Improve",
showCard : false,
cards : [],
color : "yellow"
},
{
id : 2,
title : "Action Items",
showCard : false,
cards : [],
color : "blue"
}
]
class App extends Component {
state = {list : list, usrInpt : ""}
handleChange = e => {this.setState({usrInpt:e.target.value})}
add = e => {
e.preventDefault()
let updatedList = this.state.list.map(obj => {
if(obj.id == e.target.id)
this.state.list[obj.id].cards.push(this.state.usrInpt)
return obj
})
console.log("uL",updatedList)
this.setState({list:updatedList})
//this.setState(this.state.list[e.target.id].cards = [...this.state.list[e.target.id].cards,"pp"])
}
render() {
return (
<div className="App">
<h2>Metro Board</h2>
<ul className="container">
{this.state.list.map((item) =>
<List key={(Math.random() * 1)} text={item.title}
id={item.id} cards={item.cards} color={item.color}
usrInpt={this.state.usrInpt} add={this.add} handleChange={this.handleChange}/>
)}
</ul>
</div>
)
}
}
const List = (props) =>{
return(
<li>
<h3>{props.text}</h3>
<ul className="stack">
<li><button id={props.id} type="button" className="block" onClick={e =>props.add(e)}>+</button></li>
{props.cards.map((card,i)=> {
console.log("card",card)
return <ListItem key={(Math.random() * 1)}
idx={i}
color={props.color}
handleChange={props.handleChange}
usrInpt={props.usrInpt}/>
})}
</ul>
</li>
)
}
const ListItem = (props) =>{
console.log("card props and value",props)
return <li>
<div className="card" style={{backgroundColor: props.color}}>
<textarea type="text"
className="card"
placeholder="Enter text here"
value={props.usrInpt}
onChange={e => props.handleChange(e)}>
</textarea>
<div><a className="ltCtl" href="./logo" onClick={e => console.log(e)}><</a>
<a className="clCtl" href="./logo" onClick={e => console.log(e)}>x</a>
<a className="rtCtl" href="./logo" onClick={e => console.log(e)}>></a>
</div>
</div>
</li>
}
Both List && ListItem are separate files... Any help would be great. Thanks.
UPDATE:
I was able to reach out to a full time developer and it seems I screwed up by trying to make unique keys :
The key needs to be consistent, but in this case it is a different value every time
React uses the key when it IDs which element is focusing on, but in this case, it is different than the last render. So React does not know what to focus on. You can have unique keys if you use a string with the index of the loop in it, or if you use an ID that you store outside in the loop, like in state
It does need to be unique, but it also needs to be consistent.
So the code:
return (
<Card
key={Math.random() * 1} // <--- Don't!!
idx={i}
color={props.color}
handleChange={props.handleChange}
usrInpt={props.usrInpt}
/>
);
was causing React to lose track of what to render since the keys where changing with each render. The preferred method is using a string interpolation with an identifier and an index if a loop is used:
return(
<li>
<h3>{props.text}</h3>
<ul className="stack">
<li><button id={props.id} type="button" className="block" onClick={e =>props.add(e)}>+</button></li>
{props.cards.map((card,i)=> {
console.log("card",card)
return <Card key={`card-column-${props.id}-${i}`}
idx={i}
color={props.color}
handleChange={props.handleChange}
usrInpt={props.usrInpt}/>
})}
</ul>
</li>
)
Which was also a comment made by #miyu ... which I did not test. Listen to your peers and mentors... you will not lose 12 hours chasing bugs. Thanks.
state is non-hierarchical. Meaning, when you update a child object of your state but the parent object is not updated, then react will not trigger componentDidChange.
Try adding a counter which gets updated when the list is updated.
add = e => {
e.preventDefault()
let updatedList = this.state.list.map(obj => {
if(obj.id == e.target.id)
this.state.list[obj.id].cards.push(this.state.usrInpt)
return obj
})
console.log("uL",updatedList)
let counter = this.state.counter || 0;
this.setState({list:updatedList, counter: counter++})
//this.setState(this.state.list[e.target.id].cards = [...this.state.list[e.target.id].cards,"pp"])
}

Categories