I know how to pass basic props in React but I am a bit stumped on this one. Rather than try to explain all of it in this paragraph I think showing will do better justice to the problem.
Here is how it was before trying to break it off into its own separate component.
{ <div className="flex-item-main">
<ol>
{this.state.thoughts.map((thought, index)=>
<DisplayPoem className='displayPoem' key={index} onClick={() => { this.handleDeleteClick(index) }} name='Delete Thoughts' value={thought} />
)}
</ol>
</div> }
Here is how it will look as its own separate component taking props from the parent:
{ <div className="flex-item-main">
<ol>
{this.props.thoughtsProp.map((thought, index)=>
<DisplayPoem className='displayPoem' key={index} onClick={this.props.onClick} name={this.props.name} value={thought} />
)}
</ol>
</div> }
Here is the parent component passing down the props: I have no idea what to do with onClick={() => { this.handleDeleteClick(index) }} as I need index from the .map() function in the component. I hope any of this made sense and I am happy to add updates, im not sure how to explain the problem which is probably why im having trouble solving it. Still new to React.
<DisplayPoemList thoughtsProp={this.state.thoughts} onClick={() => { this.handleDeleteClick(index) }} name='Delete Thoughts' />
You could remove the arrow function from props and pass you method as a prop.
Change this code (look at the onClick):
<DisplayPoemList thoughtsProp={this.state.thoughts} onClick={() => { this.handleDeleteClick(index) }} name='Delete Thoughts' />
To this code:
<DisplayPoemList thoughtsProp={this.state.thoughts} onClick={this.handleDeleteClick } name='Delete Thoughts' />
Then your DisplayPoemList will get a function onClick that expecting for index when it called.
Use an arrow function to create an instance of the function with an index in a scope for each element.
{ <div className="flex-item-main">
<ol>
{this.props.thoughtsProp.map((thought, index)=>
<DisplayPoem className='displayPoem' key={index} onClick={() => this.props.onClick(index)} name={this.props.name} value={thought} />
)}
</ol>
</div> }
Related
My goal is to use an onClick function in one of my components, and pass that data to another component (end goal is that in the other component called Playlist, it updates an array with the id of the clicked item).
I am just not sure how to pass the information between child components
My main component (app.jsx) looks like this
const mainCards = Data.map(card => {
return(
<MainCard
key={card.id}
id={card.id}
image={card.url}
title={card.title}
playbutton={card.playbutton}
addbutton={card.addbutton}
/>
)
})
const sideCards = SideData.map(card => {
return(
<SideCard
image={card.sideurl}
key={card.id}
title={card.sidetitle}
playbutton={card.playbutton}
addbutton={card.addbutton}
/>
)
})
return (
<div className="App">
<Navbar />
<Header />
<Playlist />
<CardContainer />
<div className="maincards">
{mainCards}
</div>
<div className="sidecards">
{sideCards}
</div>
</div>
)
}
export default App
The component where I am using onClick (MainCard.jsx)
const handleAdd = (id) => {
console.log(id)
}
return(
<div className="mainCardObject">
<div className="cardObj">
<img src={props.image} className ="mainCardImage"/>
<img src={props.playbutton} className="playbutton"/>
<img src={props.addbutton} onClick={() => handleAdd(props.id)} className="addbutton" />
</div>
</div>
)
}
export default MainCard
and the component I wish to pass information to (nothing inside, as I dont know where to start)
return(
<div className="playlistContainer">
<ul>
<li></li>
</ul>
</div>
)
}
export default Playlist```
My suggestion is that you manage 'ids array' state globally creating a context, using the hook useContext(). Here a link with a simple explanation. I hope it helps!
https://www.w3schools.com/react/react_usecontext.asp
I'm wondering if there is a way of re-factoring this code so that RepeatableAttributeSetContextProvider does not wrap all of the JSX elements but sits above them (so opens and closes before StyledHorizontalAttributesTable). When I do that at present, then values like enteredObject: "cannot be defined".
I want to try this so that I can decouple of some of the JSX that comes along with RepeatableAttributeSetContextProvider (such as labels) away from the rest of my JSX (to sit on top instead of wrap) Any ideas would be great!
return (
<div className={className}>
{objects.map((enteredObject, index) => (
<RepeatableAttributeSetContextProvider
form={form}
object={enteredObject}
key={`${enteredObject.key}-${enteredObject.repeatIndex}`}
>
<StyledHorizontalAttributesTable className="attributeset-row">
{enteredObject.attributeCollection.questions
.filter(filterRepeatAttributes)
.map((attribute) => (
<Fragment key={attribute.key}>
{renderAttribute(enteredObject, attribute, formLayout)}
</Fragment>
))}
<StyledButton
className="remove-btn"
type="link"
buttonStyle="LINK"
name="delete"
dataId={`delete-${enteredObject.key}-${index}`}
icon="bin"
onClick={() => onRemove(enteredObject)}
>
<Message id="Form.Button.Remove" defaultMessage="Remove" />
</StyledButton>
</StyledHorizontalAttributesTable>
</RepeatableAttributeSetContextProvider>
))}
</div>
);
Do you mean something like
return (
<div className={className}>
{objects.map((enteredObject, index) => (
<>
<RepeatableAttributeSetContextProvider
form={form}
object={enteredObject}
key={`${enteredObject.key}-${enteredObject.repeatIndex}`}
/>
<StyledHorizontalAttributesTable className="attributeset-row">
{enteredObject.attributeCollection.questions
.filter(filterRepeatAttributes)
.map((attribute) => (
<Fragment key={attribute.key}>
{renderAttribute(enteredObject, attribute, formLayout)}
</Fragment>
))}
<StyledButton
className="remove-btn"
type="link"
buttonStyle="LINK"
name="delete"
dataId={`delete-${enteredObject.key}-${index}`}
icon="bin"
onClick={() => onRemove(enteredObject)}
>
<Message id="Form.Button.Remove" defaultMessage="Remove" />
</StyledButton>
</StyledHorizontalAttributesTable>
</>
))}
</div>
);
But then you obviously won't have any access to the (assumed) context provided by RepeatableAttributeSetContextProvider. If you do need it, then you do need to wrap your JSX within it. If you don't need it, then why have the component at all?
Im trying to handle an aray of inputs in react which are viewed by ".map" function.
The problem is I couldn't give the field a specific value. As a result i couldn't handle the input in onChange function.
I have list of card each card will have a Admin description input and two buttons each one will send a different request.
Here A made the function that present the cards. The porplem is with the input
function createrequestcard(prop){
return(
<Card className="text-center" key={prop._id}>
<div class="wrapper">
<div id="requestspart1" class="left">
<Card.Body>
<Card.Title>Admin Description</Card.Title>
<Card.Text>
<textarea
// --> Value of the index in aray
// --> Handle Change of input
/>
</Card.Text>
</Card.Body>
</div>
<div id="requestspart3" class="left">
<Card.Body>
<Card.Title>CREATE</Card.Title>
<Button variant="outline-success" className="AdminRequestButton">APPROVE</Button>
<Button variant="outline-danger" className="AdminRequestButton">DENY</Button>
</Card.Body>
</div>
</div>
</Card>
)
}
In initialising values on class
this.state = {
requests: [],
description: '',
}
}
The request aray is updated from the backend:
componentDidMount(){
this.checkloginstatus();
axios.get('http://localhost:3000/request', {withCredentials: true})
.then(resp => {
this.setState({requests: resp.data})
}).catch(err => console.log(err))
}
And in render function:
<div>
{this.state.requests.map(createrequestcard)}
</div>
Thank you very much for helping me out.
You can pass index in map method like the below,
<div>
{this.state.requests.map((req,index) => createrequestcard(req, inex))}
</div>
function createrequestcard(prop, index){
The structure of map method is like below
map((element) => { ... } )
map((element, index) => { ... } )
map((element, index, array) => { ... } )
your card component should defined as follow, here i have named it RequestCard (just to make more readable),
this component will get handleOnChange as argument , we will pass updated value to it,when onchange event occurs in
textarea.
function RequestCard(props){
return(
<Card className="text-center" key={prop._id}>
<div class="wrapper">
<div id={props.id} class="left">
<Card.Body>
<Card.Title>Admin Description</Card.Title>
<Card.Text>
<textarea
onChange={(e)=>props.handleOnChange(e.target.value)}
// --> Value of the index in aray
// --> Handle Change of input
/>
</Card.Text>
</Card.Body>
</div>
<div id="requestspart3" class="left">
<Card.Body>
<Card.Title>CREATE</Card.Title>
<Button variant="outline-success" className="AdminRequestButton">APPROVE</Button>
<Button variant="outline-danger" className="AdminRequestButton">DENY</Button>
</Card.Body>
</div>
</div>
</Card>
)}
now you should render it as follow
<div>
{this.state.requests.map((request,index)=>{
return <RequestCard id={index} handleOnChange={(updatedValue)=>this.handleOnChange(index,updatedValue)}
})}
finally your handleOnChange of parent component should be like this,
handleOnChange=(index,updatedValue)=>{
let curState=this.state;
curState.requests[index].value=updatedValue;//note that i am not aware what member of request you want to update
//so i assume value is the member of request object,you can change whatever you want
this.setState(curState);
}
I'm working on a recipe app and I'm having some issues rendering my ingredient list from a Firebase array. I'm new to JS and React, so i apologize if this is something simple. I really appreciate any help that's provided! I've attached my code and an example of how the array is currently being rendered. I've also included a screenshot of the Firebase data structure. [firebaseStructure][1]
useEffect(() =>{
database.collection('recipes').onSnapshot(snapshot => {
setRecipeCard(snapshot.docs.map(doc => ({id:doc.id, recipes:doc.data()})));
console.log(snapshot.docs.map(doc => ({id:doc.id, recipes:doc.data()})))
})
}, []);
useEffect(() =>{
database.collection('recipes').onSnapshot(snapshot =>{
setIngredientsList(snapshot.docs.map(doc =>({id:doc.id, ingredient: doc.data().ingredients1})));
console.log(snapshot.docs.map(doc =>({id:doc.id, ingredient: doc.data().ingredients1})))
})
}, []);
return (
<div>
<Container>
<Row>
{recipeCard.map(recipes =>(
<Col md={4} key={recipes.id}>
<Card style={{ width: '100%' }}>
<Card.Img variant="top" src={recipes.recipes.url} style={{height:'300px',width:'100%'}} />
<Card.Body>
<Card.Title>{recipes.recipes.title}</Card.Title>
{/**<Card.Text> **/}
<ul key={recipes.id}>
{ingredientsList.map(recipes =>(
<li key={recipes.id}>
{recipes.ingredient}
</li>
))}
</ul>
{/** </Card.Text> **/}
<Button variant="secondary">Head to Recipe</Button>
</Card.Body>
</Card>
</Col>
))}
</Row>
</Container>
</div>
)
}
[current rendered output][2]
[1]: https://i.stack.imgur.com/bFBdr.png
[2]: https://i.stack.imgur.com/xU1fC.png
I think a lot of the confusion here has come from the naming convention, and the attempt to use separate state for the recipieCard and ingredientsList. You've ended up rendering all the ingredients for all recipies instead of just the one's relevant to that recipie. If you ever find yourself accessing properties like recipies.recipies, you know you need to do some refactoring (if you have complete control of the project).
First things first, you only need one useEffect to access your database and process the data. It all belongs together, so there's no need to separate it into separate states. Notice how I've renamed the state as recipieCards plural. This is an array or list of data objects, so it should have a pluralised name. Each data object now has the property data instead of recipies, which more accurately describes what it contains (i.e. the data for ONE recipie).
useEffect(() =>{
database.collection('recipes').onSnapshot(snapshot => {
setRecipeCards(snapshot.docs.map(doc => ({id:doc.id, data:doc.data()})));
console.log(snapshot.docs.map(doc => ({id:doc.id, data:doc.data()})))
})
}, []);
Now in the return fuction it becomes much more clear what's going on. We take the array of recipieCards and map over it to access each recipie data object one at a time. For each data object use the id as its key, and take the url and title from data. Now you can access that recipie's ingredients directly from the same data object, under the property data.ingredients1.
return (
<div>
<Container>
<Row>
{recipeCards.map(recipe =>(
<Col md={4} key={recipe.id}>
<Card style={{ width: '100%' }}>
<Card.Img variant="top" src={recipe.data.url} style={{height:'300px',width:'100%'}} />
<Card.Body>
<Card.Title>{recipe.data.title}</Card.Title>
{/**<Card.Text> **/}
<ul>
{recipie.data.ingredients1.map(ingredient =>(
<li key={ingredient}>
{ingredient}
</li>
))}
</ul>
{/** </Card.Text> **/}
<Button variant="secondary">Head to Recipe</Button>
</Card.Body>
</Card>
</Col>
))}
</Row>
</Container>
</div>
);
You don't need to call the DB twice in two separate useEffect to do what you want. You should only call the DB once for efficiency in this case.
Also, you're re declaring recipes within the second map, this could cause errors, you should avoid using the same variable name for different variables. Your problem may be related to this. I edited the following code assuming you only use the first useEffect with setRecipeCard.
{recipeCard.map(recipe =>(
<Col md={4} key={recipe.id}>
<Card style={{ width: '100%' }}>
<Card.Img variant="top" src={recipe.recipes.url} style={{height:'300px',width:'100%'}} />
<Card.Body>
<Card.Title>{recipe.recipes.title}</Card.Title>
{/**<Card.Text> **/}
<ul key={recipe.id}>
{recipe.ingredients1.map(ingredient =>(
<li key={recipes.id}>
{ingredient}
</li>
))}
</ul>
{/** </Card.Text> **/}
<Button variant="secondary">Head to Recipe</Button>
</Card.Body>
</Card>
</Col>
))}
Can you try out this code? Please let us know specifically if you are getting any errors :)
{userList.map(v => (
<Card
key={v.user}
onClick={() =>
console.log("object")
}
>
<Card.Header
title={v.user}
thumb={require(`../img/${v[tableName].avatar}.png`)}
extra={<span>{v[tableName][extra]}</span>}
/>
<Card.Body>
{v[tableName].desc.split(/\n/g).map((v, k) => (
<li key={k}>{v}</li>
))}
</Card.Body>
</Card>
))}
Click events are not available on the .map() function in React
Card Component of React material UI does not support onClick props so you should not use it.
Quick Fix would be to enclose it inside a div and add onClick on the div as shown below:
<div onClick=> {() =>
console.log("object")
}>
<Card>
.....
</Card>
</div>
Or You can use some different library from :
https://material-ui.com/api/card-action-area/