Reactjs - Need to bind a onClick handler twice? - reactjs

Coming from Vue.js (two way data flow) I have question about react one way data flow - I have a Parent that have a handler for its child onClick:
<div className="recipe-container">
<button onClick={this.toggleRecipeList.bind(this)}>Show Recipes</button>
<RecipeList showRecipe={this.showRecipe} list={this.state.recipes} />
</div>
So, I pass showRecipe handler, which has only one parameter (and simply just logs it to the console).
My RecipeList looks like this (stateless func):
return (<ul className='recipe-list-bottom'>
{
props.list.map((rec, key) => {
return <li onClick={props.showRecipe(rec)} key={key}>{rec.title}</li>
})
}
</ul>)
I tried to launch showRecipe(rec) to have the current rec object as argument. Although I recive what I want, the handler is being fired from a button which is a sibling of RecipeList.
I manage to get it working by adding onClick={props.showRecipe.bind(null,rec)} to li element, but I find it really dirty way to do so.
Am I missing something? I thought showRecipe(rec) would be enough to get what I wanted. Why showRecipe(rec) is being fired with this set to button?

I think that your second snippet has a classic error:
return (<ul className='recipe-list-bottom'>
{
props.list.map((rec, key) => {
return <li onClick={props.showRecipe(rec)/*here*/} key={key}>{rec.title}</li>
})
}
</ul>)
You are assigning the result of calling showRecipe to the onClick parameter, not the function itself. The bind solutions works, but if you want to pass the parameter rec without using bind you need to wrap the call:
return (<ul className='recipe-list-bottom'>
{
props.list.map((rec, key) => {
return <li onClick={()=>props.showRecipe(rec)} key={key}>{rec.title}</li>
})
}
</ul>)

You can use es2015 stage-0 syntax in order to write it like this:
class Blah extends Component {
onClick = (recipe) => e => this.props.showRecipe(recipe);
render() {
return (
<ul className='recipe-list-bottom'>
{
props.list.map((rec, key) => {
return <li onClick={onClick(rec)} key={key}>{rec.title}</li>
})
}
</ul>
)
}
}

Related

Mapping Double nested JSON - Object not allowed in React Child

I have a json file that looks like this:
"skills":[
{
"type":"Languages",
"skill":[
{
"name":"Python"
},
{
"name":"Java"
},
{
"name":"JavaScript"
},
{
"name":"HTML"
},
{
"name":"Bash"
},
{
"name":"MySQL"
}
]
},
{
"type": "Flavours",
"skill": [
{
"name":"Reactjs"
},
{
"name":"Angularjs"
},
{
"name":"Agile"
},
{
"name":"Waterfall"
},
{
"name":"Kanban"
}
]
},
{
"type": "Technologies",
"skill": [
{
"name":"Jira"
},
{
"name":" BitBucket"
},
{
"name":"Git"
}
]
}
]
},
And i am trying to render it using a nested mapping function:
var skills = this.props.data.skills.map((skills) =>{
var skill = skills.skill.map(function(skill){
return <li key={skill.name}>{skill}</li>
})
return <ul key={skills.type}>{skills}</ul>
})
However it says "Error: Objects are not valid as a React child (found: object with keys {name}). If you meant to render a collection of children, use an array instead."
So i tried it like this:
var skills = this.props.data.skills.map(function(skills){
var skillsArr = []
var skill = skills.skill.map(function(skill){
var skillArr = []
skillArr.push(<li key={skill.name}>{skill}</li>)
return <span>{skillArr}</span>
})
skillsArr.push(<div key={skills.type}><h3>{skills.type}</h3>
<ul>
{skill}
</ul>
</div>);
return <div>{skillsArr}</div>
})
But this too gives me the exact same error, i dont get what is wrong here because if i do a single mapping of just the skill types it works, it is only when i try to render the inner mapped items does this error occur and break my code
This is how i am calling it btw:
<div className="bars">
<ul className="skills">
{skills}
</ul>
</div>
If we are talking about using React, you should think more about how to organize your code in order to follow a proper component structure, that will let clear what you want to render and how to properly split your data and responsibilities.
Looking to your JSON, we have a set of "skills" that have skills inside it (let's call them "innerSkills").
We can easily split it into 3 components, let's think together:
We can have a List that will render all your Skills.
We can have a Skill that will be responsible for rendering each Skill data, inside it, we will need to render the InnerSkills, so let's split it to another component.
We have then InnerSkill, that will be responsible for rendering each innerSkill that we have for each skill.
So, long story short, what we have is:
List -> Skill -> InnerSkills
Great, now that we established the split, let's see how we can make each component responsible for rendering its data.
Let's say we want to simply call <List skills={data} />. Following this, we can then start on the list itself, which would look something like:
const List = ({ skills }) => (
<ul>
{skills.map((skill, i) => (
<Skill key={i} skill={skill} />
))}
</ul>
);
Now that we are looping through all Skills and calling the Skill component for rendering it, we can take a look at how Skill should look, since it will also need to loop through skill.
const Skill = ({ skill }) => (
<li>
<p>Type: {skill.type}</p>
<ul>
{skill.skill.map((innerSkill, i) => (
<InnerSkill key={i} innerSkill={innerSkill} />
))}
</ul>
</li>
);
Great. Now we already have the two loops you need to render all the data, so it's just missing the definition on how each InnerSkill should look like, and we can take a simplified approach and say we just want to render the name, so it could be something like:
const InnerSkill = ({ innerSkill }) => (
<li>
<p>Name: {innerSkill.name}</p>
</li>
);
To summarize this implementation, I made a simple code sandbox so you can See it live! and play around with the components.
I hope this clarifies your question and helps you to think better in the future on how you want to organize stuff, first check how to split, later how to render. Don't try to start rendering everything inside loops because it will get nasty.
There are two things in your code causing this error:
var skills = this.props.data.skills.map((skills) =>{
var skill = skills.skill.map(function(skill){
// should be <li key={skill.name}>{skill.name}</li>
return <li key={skill.name}>{skill}</li>
})
// should be <ul key={skills.type}>{skill}</ul>
return <ul key={skills.type}>{skills}</ul>
})
Assuming you want a single unordered list of all skill names, I'd suggest using the flatMap() function to re-write this as follows:
<div className="bars">
<ul className="skills">
{this.props.data.skills.flatMap((skillGroup) =>
skillGroup.skill.map((skill) => (
<li key={skill.name}>{skill.name}</li>
))
)}
</ul>
</div>

Reactjs, how to instanciate object from array, and update render

I'm struggling with reactjs for no reason. I'm a little confused about the magic behind and I'm not able to perform a simple operation of adding object / removing object from an array and display it.
I my parent, I have a method which on click append a new element:
appendNewPma(){
var newPma = this.state.pma.slice();
newPma.push(PmaType1);
this.setState({pma:newPma})
}
then my render method is like that:
render() {
return (
<div>
<a className="waves-effect waves-light btn" onClick={this.appendNewPma}>new</a>
{this.state.pma.map((Item, index) => (
<Item
key = {index}
ref = {"pma" + index.toString()}
onDelete = {() => this.onDelete(index)}
title = {index}/>
))}
</div>
);
}
Append work fine, but my array doesn't contain an object to display but rather a magical function that I don't understand.
But when I try to delete an object:
onDelete(idx){
console.log(idx);
var pma = this.state.pma.slice();
pma.splice(idx, 1);
this.setState({pma:pma})
}
When I delete from the array, no matter what index I will remove, it will only remove the last object. I know my code is not ok, but I have no idea how you can render element for an array of object (here my array is list of function constructor).
It will work better if I could get a straight ref to my object. Of course, I tryed to removed from the ReactDom, but was complening I was not updating from the parent...
I just want a simple array push/pop pattern with update.
Thanks for your help
Try below code. hope so it solve your issue.
addToArray = (event) => {
this.state.pma.push({"name ": "xyz"});
this.setState(
this.state
)
}
removeFromArray =(index) => {
var updatedArr = this.state.pma.splice(index, 1);
this.setState({arr : updatedArr})
}

React - Not able to update list of product component

I made a list of product which contains name, price etc.. properties. Then I created a simple search box and I am searching my products based on product name . the search result returns the correct object but the UI is not getting updated with that result.Initially, I am able to see the list but after searching it is not getting updated. I am new to react SO need some help. here is my code
OnInputChange(term)
{
let result= this.products.filter(product=>{
return product.name==term;
});
console.log(result);
let list=result.map((product)=>
{
return <li key={product.price}>{product.name}</li>
});
console.log(list);
this.setState({listOfProducts:list});
}
render()
{
this.state.listOfProducts=this.products.map((product)=>
{
return <li key={product.price}>{product.name}</li>
});
return <div>
<input onChange={event=>{this.OnInputChange(event.target.value)}}/>
<ul>{this.state.listOfProducts}</ul>
</div>
}
}`
this.products.filter should probably be this.state.products.filter
When referring to a Components method you say this.onInputChange but when referencing state you must say this.state.products because this.products doesn't exist.
I would also move this:
let list=result.map((product)=>
{
return <li key={product.price}>{product.name}</li>
});
to your render statement. so your onchange handler could be:
OnInputChange(term)
{
let result= this.products.filter(product=>{
return product.name==term;
});
this.setState({listOfProducts:result});
}
and then you
render(){
return(
{this.state.listOfProducts.map(product => {
return <li key={product.price}>{product.name}</li>
})}
)
}
hope that helps! If your problem persists please share your entire code so I can better understand the issue.

How should I fix TypeError "property 'props' of undefined"?

When I click on the button [Add to Cart] I'm getting an error:
.
How should I fix that?
Here's my full code link, and below is an abstract, that is relevant to question:
class ProductsList extends Component {
....
renderProductsList() {
function mapProductCards(elem) {
return (
<Card className="m-1" style={{width: '18rem'}} key={elem.id}>
....
<CardText>isAvailable</CardText>
<Button onClick={() => this.props.addItemToCart(elem.id)}>Add to Cart</Button>
</CardBlock>
</Card>
)
}
....
this.props.products ....
.map(mapProductCards)
....
const mapDispatchToProps = (dispatch) => ({
....
addItemToCart(value) {
dispatch(addToCart(value));
}
....
});
export default connect(mapStateToProps, mapDispatchToProps)(ProductsList);
....
At the start of your renderProductsList function, assign your this variable to a local one. Like this:
renderProductsList() {
var that = this;
function mapProductCards(elem) {
return (
and then just use that where needed. For example, your button would now be:
<Button onClick={() => that.props.addItemToCart(elem.id)}>Add to Cart</Button>
Alternatively, use an arrow function instead. Like this:
renderProductsList() {
mapProductCards = (elem) => {
return (
this will preserve the reference of this to the object you actually need.
EDIT
After looking at your full code, based on how you use your mapProductCards function, you need to pass in the correct object to use as this. You can pass that object as the second parameter to your map. Like this:
this.props.products.map(mapProductCards, this);
You might find the documentation for map()over at MDN interesting:
var new_array = arr.map(function callback(currentValue, index, array) {
// Return element for new_array
}[, thisArg])
thisArg: Optional. Value to use as this when executing callback.
I also faced this issue and the problem was when i debugged my code ,local this ie this object of the function was giving undefined object.you need to instantiate this object of the function addItemToCart because when you click the button it will give you an error on console saying
Your method addItemToCart is undefined
to overcome this you need to define local this as
renderProductsList() {
var localThis= this;
}
and then call your method as
onClick={() => localThis.props.addItemToCart(elem.id)}
If you are using a function inside a loop say map then you need to do
this.state.map(function(){
},**this**)
to preserve this

How to use parent function inside react's map function

I'm passing the following functions to the following component...
<Main css={this.props.css} select={this.selectedCSS.bind(this)} save={this.saveCSS.bind(this)} />
Then inside the Main component I am using these functions...
<h1>Select the stylesheet you wish to clean</h1>
{
this.props.css.map(function(style){
if (style) {
return (<div className="inputWrap"><input type="radio" name="style_name" onClick={this.props.select(style)}/><span></span><a key={style}>{style}</a></div>)
}
})
}
</div>
<button className="cleanBtn" onClick={this.props.save}>Clean!</button>
Notice in my map function, I am passing this.props.select(style). This is a function from the parent, and I am trying to pass to it a parameter. But when I do this, I get an error...
Error in event handler for runtime.onMessage: TypeError: Cannot read property 'props' of undefined
Every other function I pass works. I've already tested them. In fact, the code works, the only issue is when I try to pass a function inside map. What is the reason for this? How can I fix it?
I tried to add .bind(this) but it runs in an infinite loop when I do so.
The problem is that Array.prototype.map doesn't bind a this context unless explicitly told to.
this.props.css.map(function(style) {
...
}, this) // binding this explicitly
OR
this.props.css.map((style) => { // arrow function
...
})
OR
const self = this;
this.props.css.map((style) => {
... // access 'self.props.select'
})
I also see another problem with your code. Inside map you're doing this
if (style) {
return (
<div className="inputWrap">
<input type="radio" name="style_name" onClick={this.props.select(style)}/>
<span>something</span>
<a key={style}>{style}</a>
</div>
);
}
Here input element is expecting a function for its onClick, but you're actually evaluating the function by calling this.props.select(style) and passing its return value (if at all it returns something) to onClick. Instead you may need to do this:
this.props.css.map((style) => {
if (style) {
return (
<div className="inputWrap">
<input type="radio" name="style_name" onClick={() => this.props.select(style)}/>
<span>something</span>
<a key={style}>{style}</a>
</div>
);
}
})
In your mapping function, this does no longer point to the react component.
Bind the context manually to resolve this:
{
this.props.css.map((function(style) {
if (style) {
return (<div className="inputWrap"><input type="radio" name="style_name" onClick={this.props.select(style)}/><span></span><a key={style}>{style}</a></div>)
}
}).bind(this))
}
Alternatively, use an ES6 arrow function, which preserves the surrounding context:
{
this.props.css.map(style => {
if (style) {
return (<div className="inputWrap"><input type="radio" name="style_name" onClick={this.props.select(style)}/><span></span><a key={style}>{style}</a></div>)
}
})
}
You mentioned passing a parameter when calling a parent's function? As onClick wants a reference to a function (but realising that you need to pass a parameter), you could try the following:
onClick={() => { this.props.select(style) }}

Resources