How to use parent function inside react's map function - reactjs

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) }}

Related

Can't dynamically render reactjs elements using .map()

I am trying to dynamically render out ingredient/qty/measure using .map() in reactjs I am following a few tutorials online but unable to properly implement the code in my own code.
Here is the error I am currently getting:
react-dom.development.js:86 Warning: Functions are not valid as a React child.
This may happen if you return a Component instead of <Component /> from render.
Or maybe you meant to call this function rather than return it.
Here is the data I am trying to map over:
recipeIngredients: Array(2)
0: {name: 'lemon', quantity: '100', measure: 'g'}
1: {name: 'butter', quantity: '5', measure: 'cups'}
Here is my code:
import './IngredientsList.css'
let ingredientArray
function mapIngredients() {
ingredientArray.map((item) => (
<div className="ingredient-element">{item}</div>
))
}
function IngredientsList(props) {
console.log(props)
ingredientArray = props.recipeIngredients
return <div className="ingredient-list">{mapIngredients}</div>
}
export default IngredientsList
Basically, trying to render out the following set of divs (recipeIngredients.name has an additional class):
<div className="ingredient-list">
<div className="ingredient-element">recipeIngredients.quantity</div>
<div className="ingredient-element">recipeIngredients.measure</div>
<div className="ingredient-element ingredient-name">recipeIngredients.name</div>
</div>
I notice that the () are missing from the IngredientList function - VSCode keeps deleting them when I save the code so I can't manually add them...
You forgot to invoke the function. Here you're trying to render the function itself:
return <div className="ingredient-list">{mapIngredients}</div>
Instead, invoke the function and render its result:
return <div className="ingredient-list">{mapIngredients()}</div>
Additionally, the function currently doesn't return anything. Add a return statement:
function mapIngredients() {
return ingredientArray.map((item) => (
<div className="ingredient-element">{item}</div>
))
}
I'm seeing two issues here. First, you're not invoking the function(as you said that vs code is not letting you do that). The second is item is an object here. Try this instead:-
function IngredientsList(props) {
console.log(props)
ingredientArray = props.recipeIngredients || [];
return (
<div className="ingredient-list">
{
ingredientArray.map(item => (
<div className="ingredient-element">{item.name}</div>
))
}
</div>
)
}

Why function works without "this" binding?

I'm using React and Redux. I have a question. I know when I need "this" in a function I should bind it. But without binding my code works. Why ?
Function :
onSubmit() {
this.props.dispatch({type: 'ADD_TO_LIST', payload: this.state.inputValue});
}
And this is my render input :
<input type="text" placeholder="Enter Your Text ..." onChange={(e) => {
this.setState({inputValue: e.target.value})}} onKeyDown={(e) => {
if (e.key === 'Enter') {
this.onSubmit()
}
}}/>
You have idea about "when binding is required", but you missed one thing, "calling function will have the this (object by which it was called)". The value of this is determined by how a function is called.
Here:
this.onSubmit()
You are calling submit with this (class instance), so this (inside submit) will refer to class instance.
Check these references:
MDN Doc
Why is JavaScript bind() necessary?
Because you used an arrow function to call it, it's not the event handler itself. The arrow function is already bound correctly.
While function is binding (it changes this), ES6 arrow functions are non-binding. An example:
this.a = 1;
obj = {
a: 2,
bfunction: function() { return this.a; },
barrow: () => { return this.a; },
}
console.log(obj.bfunction()) // 2
console.log(obj.barrow()) // 1

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

Reactjs - Need to bind a onClick handler twice?

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>
)
}
}

Get element sibling value in React

I have this method inside a React component (which I later pass to the render() method):
renderNutrientInputs: function (num) {
var inputs = [];
for (var i =0; i < num; i++) {
inputs.push(<div key={i}>
<label>Nutrient name: </label><input type="text"/>
<label>Nutrient value: </label><input type="text" />
</div>);
}
return inputs;
}
I'm trying on each change of the "Nutrient value" textbox, to also grab the current value of the "Nutrient name" textbox. I first though of assigning "ref" to both of them, but I figured there might be multiple pairs of them on the page (and the only way to identify them would be by key). I also tried something like this:
<label>Nutrient name: </label><input type="text" ref="nutName"/>
<label>Nutrient value: </label><input type="text" onChange={this.handleNutrientValueChange.bind(null, ReactDOM.findDOMNode(this.refs.nutName))}/>
but got a warning from React:
Warning: AddForm is accessing getDOMNode or findDOMNode inside its
render(). render() should be a pure function of props and state. It
should never access something that requires stale data from the
previous render
Is there some way to attach onChange event listener to Nutrient value text box and access the current value of "Nutrient name" textbox in the event listener function?
You don't want to access DOM elements directly. There is no need to do so... Work with your data, forget about DOM!
What you want is to "listen to changes to n-th nutritient. I want to know it's name and it's value". You will need to store that data somewhere, let's say in state in this example.
Implement getInitialState method. Let's begin with empty array, let user to add nutritients.
getInitialState() {
return { nutritients: [] };
},
In render method, let user add nutrition by click on "+", let's say
addNutritient() {
const nutritients = this.state.nutritients.concat();
nutritients.push({ name: "", value: undefined });
this.setState({ nutritients });
},
render() {
return (
<div>
<div onClick={this.addNutritient}>+</div>
</div>
)
}
Okay, let's focus on rendering and updating nutritients:
addNutritient() {
const nutritients = this.state.nutritients.concat();
nutritients.push({ name: "", value: undefined });
this.setState({ nutritients });
},
renderNutritients() {
const linkNutritient = (idx, prop) => {
return {
value: this.state.nutritients[idx][prop],
requestChange: (value) {
const nutritients = this.state.nutritients.concat();
nutritients[idx][prop] = value;
this.setState({ nutritients });
},
}
};
const nutritients = [];
return (
this.state.nutritients.map((props, idx) => (
<div>
<input valueLink={linkNutritient(idx, "name")} />
<input valueLink={linkNutritient(idx, "value")} />
</div>
))
)
},
render() {
return (
<div>
{ this.renderNutritients() }
<div onClick={this.addNutritient}>+</div>
</div>
)
}
Coding by hand, sorry for syntax error or typings.
Edit:
Take a look at this working Fiddle https://jsfiddle.net/Lfrk2932/
Play with it, it will help you to understand what's going on.
Also, take a look at React docs, especialy "valueLink" https://facebook.github.io/react/docs/two-way-binding-helpers.html#reactlink-without-linkedstatemixin
I prefer not to use 2 way binding with React which is kind of a flux anti-pattern. Just add a onChange listener to your input element and setState.
Your state will be something like this:
{0: {nutrientName: xyz, nutrientValue: 123},
1: {nutrientName: abc, nutrientValue: 456}}
So when you change the nutrientvalue 456 to say 654, you can say its corresponding name is abc and vice versa.
The whole thing about React is about handling the data not the DOM :)

Resources