JSX binding this to a function within a map - javascript

I am using a map function and have tested and confirmed that within the maps function this is referencing the class object as desired.
When I try passing this again by binding it to a jsx function, it doesn't pass this as desired.
Error: Uncaught TypeError: _this3.checkType(...).bind is not a function
// wrapped in a class
checkType(type, options, placeholder, name, handleUpdatedValue, defvalue, index) {
console.log(this);
switch(type) {
case 'select':
return <select onChange={handleUpdatedValue.bind(this)} >{options.map((option, i) => <option value={option} key={i}>{option}</option>)}</select>
break;
case 'text':
return <input onChange={handleUpdatedValue.bind(this)} name={name} placeholder={placeholder} type="text" />
break;
case 'date':
return <input onChange={handleUpdatedValue.bind(this)} name={name} placeholder={placeholder} type="date" />
break;
default:
console.log('Error: Invalid Type');
}
return type === 'select' ? <select></select> : <input />
}
return(
<div className="formwrapper thinshadow">
<h3>{this.props.header}</h3>
{this.getFields().map((field, i) => {
<div key={i} className={field.classes}>
{this.checkType(field.type, field.options, field.placeholder, field.name, this.handleUpdatedValue.bind(this), field.defvalue, field.index).bind(this)}
// DOES NOT WORK ^
<div className="minilabel"></div>
</div>
}, this)} // THIS WORKS
<button className="btn btn-primary"
onClick={() => this.props.onClick(values)} >
Save
</button>
</div>
);

You are invoking the function instead of binding it.
this.checkType(field.type, field.options, field.placeholder, field.name, this.handleUpdatedValue.bind(this), field.defvalue, field.index).bind(this)
should be
this.checkType.bind(this, field.type, field.options, field.placeholder, field.name, this.handleUpdatedValue.bind(this), field.defvalue, field.index)
bind Usage
fun.bind(thisArg[, arg1[, arg2[, ...]]]
Refer to this link for more details:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
Edit
You use bind when you are passing your function to another context (another this pretty much), and still want the function to be invoked under the original this.
In your situation, you don't need to bind because within your map, you have the same this. See You Don't Need to Bind This in React when using forEach and map.
But if you do want to, I strongly advise against binding when you are iterating. You can use closure, and bind before the iteration.
bind is slow, even though it doesn't really matter most of the time. But more importantly, you don't want to bind in iterations like forEach or map because it's easy to lose the context (this) without knowing (when you are not using React). And you will scratch you head for a few hours.
If you are using React, you can bind in the render function.
render: () {
const checkType = this.checkType.bind(this);
render (
map((node) => { checkType(...) });
);
}

Related

Pass a multiple arguments from a child props to the parent method

I am new to react js . Here, what I am trying to do is that , I have a parent component which is like:
onchange(event) {
console.log("function will be callled", event.target.value, event.target.id);
{ (this.props.lowData) && this.props.lowData.Low.length > 0 && this.props.lowData.Low.map(data => (
<LowRow technologies={this.state.technologies} onChange={this.onchange.bind(this)} data={data} key={data.id} />
))}
Here there is a onchnage method I am passing as a props to the child element, which is:
<select className="selectpicker btn btn-labeled btn-start selectId techDrop margin-left-10" onChange={props.onChange}>
<option disabled selected value>None Selected</option>
{props.technologies && <Select techData={props.technologies} options={props.technologyName} />}
</select>
So, Now what I want to do is that, In the child when user changes:
onChange={props.onChange}
this gets called in parent element, so here I also want to pass one more parameter with this like:
onChange = {props.onChange, props.id}so that Parent will get one Id as well, But its not working . And also I tried with the `props.onChange(props.id)` But no luck. Can any one help me with this ?
Parent onchange function will receive id in 2nd argument.
onchange(event, id) {
console.log("function will be callled", event.target.value, id);
}
While calling from child you call like this.
onChange={(e)=> {props.onChange(e, props.id)}}
First thing you are not recommended to bind a function directly in render. Because when you bind a function directly in render your component will render for many reasons like setState, when new props received etc. so every time it renders it will create a new function in webpack generated bundle file so the file size becomes large. so always do binding in constructor
Now regarding your issue. Do map in render directly and pass onChange as a prop down to LowRow component. You need to do something like below to pass a function as a prop and send params to it in child component and access it in parent component. This concept is called callbacks in react.
One more thing never forget to add a key to parent jsx element whenever you generate jsx elements in loop. The key should be a unique id from data. If your data don't conatin unique id then pass index as key like key={"key" +index}
constructor(props){
super(props);
this.onChange = this.onChange.bind(this);
}
onChange(event, id) {
console.log("test", event, id);
}
render(){
const { lowData } = this.props;
return(
<div>
{this.props.lowData && this.props.lowData.Low.map(data => (
<LowRow key={data.id} technologies={this.state.technologies} onChange={this.onChange} data={data} key={data.id} />
))}
</div>
)
}
Select onChange
Here not sure what id are you passing. but you can pass params like how I did in the below code
<select className="selectpicker btn btn-labeled btn-start selectId techDrop margin-left-10" onChange={e => props.onChange(e, id)}>
<option disabled selected value>None Selected</option>
{props.technologies && <Select techData={props.technologies} options={props.technologyName} />}
</select>

why is 'this' suddenly out of scope for me?

I think I'm going crazy. I have 2 nearly identical pieces of code and in 1 of them the keyword this references the correct scope while in the other it doesn't. I've been staring at it for 3 hours and need other eyes.
The first function is this:
renderField({input, options, label, name, multi}){
let list = options.map(category=>{
return {value:category.name, label:category.name}
});
return(
<div>
<label>{label}</label>
<Select
value={this.state.selected}
multi={multi}
name={name}
className="basic-multi-select"
classNamePrefix="select"
options={list}
onChange={(e)=>{
this.setState({selected:e});
input.onChange(e);
}}
/>
</div>
)
}
the this I'm referring to is the line this.setState({selected:e});. This code works. the 'this' is called in the correct scope. I needed to refactor the code so I wrote another function in a higher level component and bound it to that class. I then proceeded to chane the above to the following:
renderField({defaultValue, input, options, label, name, multi, initialValues}){
let list = options.map(category=>{
return {value:category.name, label:category.name}
});
return(
<div>
<label>{label}</label>
<Select
value={this.props.selected}
multi={multi}
name={name}
className="basic-multi-select"
classNamePrefix="select"
options={list}
onChange={(e)=>{
this.props.changeState(this.props.state_handler, e);
input.onChange(e);
}
}
/>
</div>
)
}
suddenly this is no longer in scope and is now pointing at the e argument that I'm passing in. Why is this happening and how can I fix it?
In event handler, this means the event target.
So you should set a variable to remember the this in renderField.
Here's an example with comment.
renderField({defaultValue, input, options, label, name, multi, initialValues}){
let list = options.map(category=>{
return {value:category.name, label:category.name}
});
// set that to current this
let that = this;
return(
<div>
<label>{label}</label>
<Select
value={this.props.selected}
multi={multi}
name={name}
className="basic-multi-select"
classNamePrefix="select"
options={list}
onChange={(e)=>{
// this.props.changeState(this.props.state_handler, e);
// use that instead
that.props.changeState(that.props.state_handler, e);
input.onChange(e);
}
}
/>
</div>
)
}

Object keys not displaying anything in react

I have these (from what i can see) almost identical ways of doing what i want, yet the object.keys way is not displaying anything in my browser.
first way:
{this.state.months.map((month, index) => {
for (var key in month) {
if (month.hasOwnProperty(key)) {
return <div key={index} className="match">
<input className="toggle" type="checkbox" />
<label htmlFor="toggle">{month[key].month}</label>
<div className="expand">
{month[key].p1.name} {month[key].p1.score} {month[key].p2.name} {month[key].p2.score}
</div>
</div>
}
}
})}
second way:
{this.state.months.map((month, index) => {
for (var key in month) {
Object.keys(month).forEach((mnth)=>{
console.log(month, 'm1');
console.log(mnth, 'm2');
return <div key={index} className="match">
<input className="toggle" type="checkbox" />
<label htmlFor="toggle">{month[mnth].month}</label>
<div className="expand">
{month[mnth].p1.name} {month[mnth].p1.score} {month[mnth].p2.name} {month[mnth].p2.score}
</div>
</div>
})
}
})}
the reason i want to use object.keys is because it is allowing me to iterate through multiple object properties. the first way was only showing the first key in each object
when i console.log (month[mnth]) I get exactly what i want.
there are also no errors in the server console or the browser console. any ideas why it wont render? im changing nothing else and consistently doesn't render anything
I think you just use template ES6 string. Wrap your print elements like this
`${month[mnth].p1.name}` `${month[mnth].p1.score} ` `${month[mnth].p2.name} ` `${month[mnth].p2.score}`
You also need to return your elements.
You can remove the outer for loop and just use map in iterating thru the keys.
{this.state.months.map((month, index) => {
Object.keys(month).map((mnth, mnthIndex)=>{
console.log(month, 'm1');
console.log(mnth, 'm2');
return (<div key={`${index}-${mnthIndex}`} className="match">
<input className="toggle" type="checkbox" />
<label htmlFor="toggle">{month[mnth].month}</label>
<div className="expand">
{`${month[mnth].p1.name} ${month[mnth].p1.score} ${month[mnth].p2.name} ${month[mnth].p2.score}`}
</div>
</div>)
})
})}
also, observe the use of combination if indexes in the second iteration as key to avoid similar keys. this could also be the reason why there's nothing being rendered in the browser.

creating elements in React

I don't understand how elements are created in React.
I have some code below where the goal is to create elements on a form submit using a value from a refs - so for every submit in a form, it creates a new <h1> tag with the content of the textbox inside of it. A sample of what I'm trying to do looks like:
...
addHeader(e) {
e.preventDefault();
const newHeader = this.refs.post.value;
var newpost = React.createElement("h1", {
type: "text",
value: newHeader
});
}
...
render() {
return (
<div className="form-section">
{ newPost }
<form onSubmit={this.addHeader.bind(this)}>
<input id="input-post" type="text" placeholder="Post Here" ref="post" />
<input type="submit" value="Submit" />
</form>
<button className="form-section__submit" onClick={this.clearFields.bind(this)}>Clear All</button>
</div>
);
}
Basically my thinking is in my addHeader() function I'm assigning a variable of newPost to the method and calling it within my component. This code is causing 2 errors:
33:9 warning 'newpost' is assigned a value but never used no-unused-vars
49:13 error 'newPost' is not defined no-undef
What I don't understand, is (from what I can see) I am assigning a value to that variable and also using it in the component that I am rendering... along with that, I don't understand this error message. How can something be assigned a value but be undefined at the same time...? Is it because it's in the wrong scope? How do I declare where the new element is rendered specifically in the component?
I read the documentation but it doesn't give a clear answer as to how to control where in the component the new element is rendered.
Made some changes to your code. You're going to want to initialize component state in your constructor. In your addHeader method you will use this.setState to update the state of the component with a new posts value including the value of this.input. I changed your ref on the input an actual ref. You take the element and store on this. Every time you add a new post you will get a new <h1> with the value of the textarea.
...
addHeader(e) {
e.preventDefault();
this.setState((prevState, props) => {
return { posts: [ ...prevState.posts, this.input.value ] };
});
}
...
render() {
const { posts } = this.state;
return (
<div className="form-section">
{ posts.map( text => <h1>{ text }</h1> ) }
<form onSubmit={this.addHeader.bind(this)}>
<input id="input-post" type="text" placeholder="Post Here" ref={ el => this.input = ref } />
<input type="submit" value="Submit" />
</form>
<button className="form-section__submit" onClick={this.clearFields.bind(this)}>Clear All</button>
</div>
);
}
As an aside: Binding functions in the render method of react components will cause a performance hit. There is no need to re-bind the this context of the function on every render. this.clearFields.bind(this) should become this.clearFields and you will need to add this.clearFields = this.clearFields.bind(this) to your constructor. You do not need to bind functions that are not used as callbacks.
You're going to want to do the same thing for this.addHeader.bind(this).

Traversing children's children and adding function to all inputs while keep other children untouched

I have been trying to get this to work for a while now and not sure how to do the following. My form component has children that contain regular html markup as well a inputs. If the child is a Input I want to add the attachToForm and detachFromForm functions. If it is not an input I want to continue traversing the children to make sure that the element does not have a child input field. Wether or not the element is an input I still want it to appear on my page, I just want to add the functions to the inputs.
The problem is I can only get my function to return only the inputs, removing the labels and title. I know that is because Im only adding elements with inputs to newChildren, but if I push the other elements in the else if section I get duplicates and i can think of another way of doing this. Im not sure if im not understanding basic JS or having a brain gap.
React.Children.forEach(children, function(child) {
var current = child;
if (child.props && child.props.name) {
this.newChildren.push(React.cloneElement(child, {
detachFromForm: this.detachFromForm,
attachToForm: this.attachToForm,
key: child.props.name
}));
} else if (child.props && child.props.children){
this.newChildren.push(child);
this.registerInputs(child.props.children);
} else {
*need to keep track of parent elements and elements that do not have inputs
}
}.bind(this));
Edit: Not sure if needed but this is and example form im traversing
return (
<Modal className="_common-edit-team-settings" title={`Edit ${this.props.team.name}`} isOpen={this.props.modalIsOpen && this.props.editTeamModal} onCancel={this.props.toggleEditTeamModal} backdropClosesModal>
<Form onSubmit={this.saveChanges}>
<FormSection className="edit-team-details" sectionHeader="Team Details">
<FormField label="Name">
<Input name="name" value={this.state.values.name} onChange={this.handleInputChange} type="text" placeholder={this.props.team.name}/>
</FormField>
<FormField label="Mission">
<Input name="mission" value={this.state.values.mission} onChange={this.handleInputChange} type="text" placeholder={this.props.team.kitMission || 'Kit Mission'} multiline />
</FormField>
</FormSection>
<FormSection className="privacy-settings" sectionHeader="Privacy Settings">
<FormField label="Included in global search results" >
<SlideToggle name="globalSearch" defaultChecked={this.state.values.globalSearch} onChange={this.handleCheckedChange} type="checkbox" />
</FormField>
<FormField label="Accessible by anyone" >
<SlideToggle name="public" defaultChecked={this.state.values.public} onChange={this.handleCheckedChange} type="checkbox" />
</FormField>
<FormField label="Secured with WitCrypt" >
<SlideToggle name="witcryptSecured" defaultChecked={this.state.values.witcryptSecured} onChange={this.handleCheckedChange} type="checkbox" />
</FormField>
</FormSection>
<FormSection sectionHeader="Participants">
{participantsList}
<div id="add-participant" className="participant" onClick={this.toggleAddParticipantModal}>
<span className="participant-avatar" style={{backgroundImage:'url(/img/blue_add.svg)'}}></span>
<span>Add a Participant</span>
<span className="add-action roll"><a></a></span>
</div>
</FormSection>
<Button type="hollow-primary" size="md" className="single-modal-btn" block submit>Save</Button>
</Form>
<AddParticipant people={this.props.people} toggleAddParticipantModal={this.props.toggleAddParticipantModal} modalIsOpen={this.props.modalIsOpen} toggleAddParticipantModal={this.toggleAddParticipantModal} addParticipantModal={this.state.addParticipantModal} />
</Modal>
);
As an aside I started out a lot simpler wanting to do the following but get:
"Can't add property attachToForm, object is not extensible"
If anyone knows why please let me know.
registerInputs: function (children) {
React.Children.forEach(children, function (child) {
if (child.props.name) {
child.props.attachToForm = this.attachToForm;
child.props.detachFromForm = this.detachFromForm;
}
if (child.props.children) {
this.registerInputs(child.props.children);
}
}.bind(this));
}
Judging of an error message, you have a problem with immutable prop object. Starting from React 0.14 the prop is "frozen":
The props object is now frozen, so mutating props after creating a component element is no longer supported. In most cases, React.cloneElement should be used instead. This change makes your components easier to reason about and enables the compiler optimizations mentioned above.
Blog post on this
So somewhere in your code you try to extend a prop object causing an error.
You could wrap different parts of your prop interactions with try..catch construction which will point you the exact problem place.

Categories

Resources