The app is loading multiple profiles from an API. When a profile's corresponding button is clicked, that profile's grades should be displayed. The issue is when a button is clicked all profile grades are being displayed.
const Profiles = () => {
const [expand, setExpand] = useState(false)
const ToggleGrades = () => {
setExpand(prev => !prev)
}
return (
<>
<div className="profile-container">
<div className="profile-info">
<h1 className="profile-name">{profile.firstName} {profile.lastName}</h1>
<p className="profile-info">Email: {profile.email}</p>
<p className="profile-info">Company: {profile.company}</p>
<p className="profile-info">Skill: {profile.skill}</p>
<p className="profile-info">Average: {profile.grades}%</p>
{
expand &&
<ul>
<li key={profile.id[0]} className="profile-grades">Test 1: {profile.grades[0]}</li>
<li key={profile.id[1]} className="profile-grades">Test 2: {profile.grades[1]}</li>
<li key={profile.id[2]} className="profile-grades">Test 3: {profile.grades[2]}</li>
<li key={profile.id[3]} className="profile-grades">Test 4: {profile.grades[3]}</li>
<li key={profile.id[4]} className="profile-grades">Test 5: {profile.grades[4]}</li>
<li key={profile.id[5]} className="profile-grades">Test 6: {profile.grades[5]}</li>
<li key={profile.id[6]} className="profile-grades">Test 7: {profile.grades[6]}</li>
<li key={profile.id[7]} className="profile-grades">Test 8: {profile.grades[7]}</li>
</ul>
}
</div>
<div className="profile-grades-expander">
<button className="profile-expand-button" onClick={ToggleGrades}>{expand ? "-" : "+"}</button>
</div>
</div>
</>
);
}
export default Profiles;
You should specify which profile to expand, try to save the id of clicked profile in a state, and use this to check whether you should expand the profile or not.
Note:
Replace in the code profile.id with the correct one, I don't know the structure of the object profile.
const Profiles = () => {
const [profileToExpand, setProfileToExpand] = useState()
const ToggleGrades = (id) => {
setProfileToExpand(id)
}
return (
<>
<div className="profile-container">
<div className="profile-info">
<h1 className="profile-name">{profile.firstName} {profile.lastName}</h1>
<p className="profile-info">Email: {profile.email}</p>
<p className="profile-info">Company: {profile.company}</p>
<p className="profile-info">Skill: {profile.skill}</p>
<p className="profile-info">Average: {profile.grades}%</p>
{(profileToExpand === profile.id) &&
<ul>
<li key={profile.id[0]} className="profile-grades">Test 1: {profile.grades[0]}</li>
<li key={profile.id[1]} className="profile-grades">Test 2: {profile.grades[1]}</li>
<li key={profile.id[2]} className="profile-grades">Test 3: {profile.grades[2]}</li>
<li key={profile.id[3]} className="profile-grades">Test 4: {profile.grades[3]}</li>
<li key={profile.id[4]} className="profile-grades">Test 5: {profile.grades[4]}</li>
<li key={profile.id[5]} className="profile-grades">Test 6: {profile.grades[5]}</li>
<li key={profile.id[6]} className="profile-grades">Test 7: {profile.grades[6]}</li>
<li key={profile.id[7]} className="profile-grades">Test 8: {profile.grades[7]}</li>
</ul>
}
</div>
<div className="profile-grades-expander">
<button className="profile-expand-button" onClick={() => ToggleGrades(profile.id)}>{expand ? "-" : "+"}</button>
</div>
</>
)
})}
</div>
}
</div>
</>
);
}
export default Profiles;
The code example you posted seems incomplete. But reading between the lines, it seems that you have one variable "expand", defined in your Profiles component, which is used by all the profiles.
You should use nested components: A ProfileList component, and a SingleProfile component. The expand state variable should be in the SingleProfile component.
Related
I am making a ordering app which has a state of array of object as shopping cart looks like below: (The state is initialized in App.js)
setCart([
...cart,
{
id: id,
quantity: drinkQuantity,
title: selectedProduct.caption,
title_cn: selectedProduct.caption2,
price: tempPrice * drinkQuantity,
variety: variety[varietyIndex].caption,
modCountObj: modCountObj,
uniqueCountObj: uniqueCountObj,
},
]);
Then I rendered the cart in a modal in Cart.js(passed the cart state from app.js) by mapping through the array with a counter that can control the quantity of each item
{cart?.map((item, index) => {
return (
<li class=" btn list-group-item h2 m-3" key={index}>
<div>
<div class="row fw-bold">
{chinese ? item.title_cn : item.title}
</div>
<div class="row fw-bold ">
<div className="col-4 ">{item.variety}</div>
<div className="col-4 "></div>
<div
className="col-1 btn btn-sm btn-outline-primary py-0 "
name="decreaseDrinkQuantity"
onClick={onClickDecreaseQuantity}
>
-
</div>
<div className="col-1 ">{item.quantity}</div>
<div
className="col-1 btn btn-sm btn-outline-primary py-0 "
name="increaseDrinkQuantity"
>
+
</div>
</div>
</div>
...
my onClick function:
const onIncreaseQuantity = (index) => {
setCart((prevState) =>
prevState.map((item, o) => {
if (index === o) {
return {
...item,
quantity: item.quantity + 1,
};
}
return item;
})
);
};
Error Message:
Warning: Cannot update a component (`App`) while rendering a different component (`Cart`). To locate the bad setState() call inside `Cart`, follow the stack trace as described in https://reactjs.org/link/setstate-in-render
at Cart (http://localhost:3000/main.77d8967….hot-update.js:29:5)
at div
at App (http://localhost:3000/static/js/bundle.js:53:80)
at QueryClientProvider (http://localhost:3000/static/js/bundle.js:56754:21)
overrideMethod # react_devtools_backend.js:4012
printWarning # react-dom.development.js:84
error # react-dom.development.js:57
warnAboutRenderPhaseUpdatesInDEV # react-dom.development.js:27489
scheduleUpdateOnFiber # react-dom.development.js:25496
dispatchSetState # react-dom.development.js:17525
onIncreaseQuantity # Cart.js:82
(anonymous) # Cart.js:123
Cart # Cart.js:102
I am guessing the problem is that I tried to change the state while rendering it but I am not sure how to solve this.
Thanks for your expertise.
At first I tried making a temp variable to store cart and change the content then setCart(tempCart) but this will increase the quantity of all elements of the whole array
let tempCart = cart;
tempCart[index].quantity += 1;
console.log("+");
setCart(tempCart);
I want to change the quantity of the item that was clicked instead of all the items in the array.
Update 30-Jan:
I have changed my screenshots to code snippets.. I am really sorry about the inconvenience and unclear. Please let me know if I can provide more info.
I want to return a return a structure like :
<div>
<div label=Category 1>
<ul>
<li>item1</li>
<li>item2</li>
</ul>
<br/>
</div>
<div label=Category 2>
<ul>
<li>item3</li>
<li>item4</li>
</ul>
<br/>
</div>
</div>
In order to achieve this, I created a Map() object an I want to iterate through categories and items. Desperately tried all sorts of ideas with no luck. I am not sure if is very simple or impossible in react. I am imagining to run stg. like this and 1) yet it does not return any value 2) splitting the ul tag is not allowed, apparently.
for (let [key, values] of lists) {
<div label={key}><ul>;
values.map(item => <li key={item.id}>{item.name}</li>);
</ul>
</div>
}
This is a working version of #Expressd3v's code (possibly with a couple of edits). I tried to add this to #Expressd3v answer but the edit's not been approved so maybe that wasn't the right way to go about it. Please don't upvote this as it's basically #Expressd3v answer.
Edited to use Map().
const Lists = ({ lists }) =>
<React.Fragment>
{
[...lists].map(([key, values]) => (
<div label={key} key={key}>
<ul>
{ values.map(item => <li key={item.id}>{item.name}</li>) }
</ul>
</div>
))
}
</React.Fragment>;
const App = () => {
let lists = new Map();
lists.set('Category 1', [
{ id: 1, name: 'item1' },
{ id: 2, name: 'item2' },
]);
lists.set('Category 2', [
{ id: 3, name: 'item3' },
{ id: 4, name: 'item4' },
]);
return <Lists lists={lists} />;
};
ReactDOM.render(<App />, document.getElementById('root'));
<script crossorigin src="https://unpkg.com/react#17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#17/umd/react-dom.development.js"></script>
<div id="root"></div>
Missing
For loop is not returning any value.
So you need to include a return.
for example:
for (let [key, values] of lists) {
return(
<div label={key}>
<ul>
{values.map(item => <li key={item.id}>{item.name}</li>);}
</ul>
</div>
)
}
My suggestion
Object.entries(lists).map(([key,values])=>(
<div label={key} key={key}>
<ul>
{values.map(item => <li key={item.id}>{item.name}</li>)}
</ul>
</div>
)
))
I'm having a strange problem creating a list with Vuejs. I have an array like so:
const sr = new Vue({
el: '#singlerecipe-app',
data: {
checkeditems: [],
},
});
And this is instantiated in my HTML like so:
<div class="container" id="singlerecipe-app">
<singlerecipe :checkeditems="checkeditems"></singlerecipe>
</div>
And declared as a Prop in my component:
Vue.component('singlerecipe', {
props: ['checkeditems'],
template: `
<ul>
<li v-for="item of checkeditems">
{{ item.checkeditems }}
</li>
</ul>
`
})
The checked items code is:
<li v-if="result.strIngredient1">
<input type="checkbox" :value="result.strIngredient1" :id="result.strIngredient1" v-model="checkeditems"> {{result.strIngredient1}} - {{result.strMeasure1}}
</li>
<li v-if="result.strIngredient2">
<input type="checkbox" :value="result.strIngredient2" :id="result.strIngredient2" v-model="checkeditems"> {{result.strIngredient2}} - {{result.strMeasure2}}
</li>
If I click on one of the checkbox it definitely adds the correct value to the array checkeditems[] but oddly, it creates the new list items but doesn't add the value so I end up with this:
<ul>
<li></li>
<li></li>
</ul>
With no value in the list item. Does anyone have any idea what I'm doing wrong?
I think you've just accidentally added a 'checkeditems' in.
It should be:
Vue.component('singlerecipe', {
props: ['checkeditems'],
template: `
<ul>
<li v-for="item of checkeditems">
{{ item }}
</li>
</ul>
`
})
I am trying to render several inputs (name, location, # of plants and 2 dates).
The 2 dates dont want to get displayed for some reasons they stay in an array format... I tried trooble shooting in several ways but nothing does :(
Any idea?
Here is the App part
let InitialOwners = [
{ nameOwner: 'Julie S', locationOwner: "Eixample", plantsOwner: "2" , startDateOwner: [{}] , endDateOwner : [] }
];
function App() {
const [owners, setOwners] = useState(InitialOwners);
// to get the data from PO form
function handleOwnerData(ownerData) {
let newOwners = [...owners, ownerData];
setOwners(newOwners)
console.log(`Owner Data: ${ownerData.locationOwner} ${ownerData.nameOwner} ${ownerData.plantsOwner} ${ownerData.startDateOwner} ${ownerData.endDateOwner}`)
}
Here is the list part where the data should be displayed
import React from "react";
function DashboardUsers(props){
return (
<div className ="Dashboard">
<h2> Welcome to the Dashboard</h2>
<h4> Here are the current owners </h4>
<ul>
{
props.owners.map((owners,i) => (
<div key={i}>
<li> Name: {owners.nameOwner}</li>
<li> Location: {owners.locationOwner} </li>
<li> # of plants: {owners.plantsOwner} </li>
<li> Start Date: [owners.startDateOwner] </li>
<li> End Date:[owners.endDateOwner] </li>
</div>
))
}
</ul>
</div>
)
}
export default DashboardUsers;
And here is how it looks like on the browser :D
browser image
When you are mapping over an array with objects each entry in the map, in your case 'owners', represents the current object. In that case owners.startDateOwner is an array of objects inside an array of objects. If you want to iterate over it you need to nest another map:
{
props.owners.map((owners,i) => (
<div key={i}>
<li> Name: {owners.nameOwner}</li>
<li> Location: {owners.locationOwner} </li>
<li> # of plants: {owners.plantsOwner} </li>
<li> Start Date: {owners.startDateOwner.map((ownerStartDate) => <span>{ownerStartDate}</span>)} </li>
<li> End Date:[owners.endDateOwner] </li>
</div>
))
}
this will iterate over the dates and print them in a <span>
Well, the question is very self-explanatory. I have this code here (inside a render, of course):
const images = [('http://placehold.it/100x100/76BD22'), ('http://placehold.it/100x100/76BD23')];
// ProductPage Views
const ProductPageView =
<section className="page-section ps-product-intro">
<div className="container">
<div className="product-intro">
<div className="product-intro__images">
<div className="product-gallery">
<ul className="product-gallery-thumbs__list">
{images.map(function(image, imageIndex){
return <li key={ imageIndex }>{image}</li>;
})}
</ul>
</div>
</div>
</div>
</div>
</section>
The thing is I don't know how to iterate those images on the array. What's wrong with my code?
Your array is an array of image URLs, not image tags. So your code is close but you need to put the image inside of an image tag inside of your list item tag.
const images = [('http://placehold.it/100x100/76BD22'), ('http://placehold.it/100x100/76BD23')];
// ProductPage Views
const ProductPageView =
<section className="page-section ps-product-intro">
<div className="container">
<div className="product-intro">
<div className="product-intro__images">
<div className="product-gallery">
<ul className="product-gallery-thumbs__list">
{images.map(function(imageSrc) {
return (
<li key={ imgSrc }>
<img src={ imgSrc } />
</li>
);
})}
</ul>
</div>
</div>
</div>
</div>
</section>
I would also recommend against using an array index as a key in general. The imgSrc is unique so it would make a good key here.
Also, make sure to include an alt attribute on the img for screen readers. You might want to make your array like this:
const images = [
{ src: 'http://placehold.it/100x100/76BD22', alt: 'Your description here 1' },
{ src: 'http://placehold.it/100x100/76BD23', alt: 'Your description here 2' }
];
// ...
{images.map(function(imageProps) {
return (
<li key={ imageProps.src }>
<img src={ imageProps.src } alt={ imageProps.alt } />
</li>
);
})}
I assume you want to display them as images, right? You should use img tag then.
<ul className="product-gallery-thumbs__list">
{images.map(function(image, imageIndex){
return <img key={ imageIndex } src={ image } />
})}
</ul>