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>
)
}
Related
I've made such a table to put articles with their respective prices, quantity... What's more there's a button and with every click it's created a new article. (put a photo to see it clearer)
The fact is that I need a way to differentiate every line, what i say is that in the square "Num" there should be 1,2,3... this is very important to me because later I need to send the information to a database, but my problem is I don't know how to differentiate every line. Here I put the code
class LinArt extends Component{
constructor(){
super()
this.state = {
quan:2,
price:20,
dto:10,
count:1
}
this.newArt = this.newArt.bind(this)
}
newArt(){
this.setState({count:this.state.count+1})
}
showArt(){
const total = (this.state.cant * this.state.preu) * (1-(this.state.dto/100));
let lines =[];
for(let i=0; i < this.state.count; i++){
lines.push(
<div key={i}>
<TextField type="number" disabled={true} value={this.state.count} label="Num"/>
<TextField label="DescripciĆ³" variant="outlined"/>
<TextField type="number" label="Cant" value={this.state.quan}/>
<TextField type="number" label="Price" value={this.state.price}/>
<TextField type="number" label="Dto" value={this.state.dto }}/>
<TextField type="number" disabled={true} value={total} label="Total"/>
<TextField type="number" disabled={true} value="21" label="IVA"/>
<br/><hr/>
</div>
)
}
return linies || null;
}
render(){
return(
<div>
{this.showArt()}
<Button onClick={this.newArt}>New article </Button>
</div>
)
}
Thank you for your attention, I appreciate so much your help!
Looking at your code, if you want the num column to increment, you should add i + 1 increment variable as the value to your num field so as it runs the loop, it gives the incremented value and starts from 1 rather than 0. Like so,
<TextField type="number" disabled={true} value={i + 1} label="Num"/>
My input element doesn't let me type into it, it will show up, with the value i want, but I cannot delete or edit anything in the input. Everything is spelled correctly from what I have seen.
I've been googling and have searched multiple SO forms, none have been able to help me. Which is why I am asking now
const GuestName =(props)=>{
if(props.isEditing){
return(
<form>
<input type='text' className='edit-guest-input'
value={props.guestName} onChange={e => props.editGuest(e.target.value)}/>
</form>
)
}
return (
<span>{props.guestName}</span>
)
}
the above is the input that will not work
editGuest=(id, name)=>{
this.setState({
guest: this.state.guest.map((guest) =>{
if(id===guest.id){
return{
...guest,
name
}}
return guest
})
})
}
this is the code for the onChange event.
const GuestList =(props)=>
<div>
<ul>
{props.guest.filter( guest => !props.filterUnconfirmedGuest || guest.isConfirmed)
.map((guest, index) => <Guest
key={index}
guestName={guest.guestName}
toggleGuestConfirmed={props.toggleGuestConfirmed}
isConfirmed={guest.isConfirmed}
toggleEditGuest={()=> props.toggleEditGuest(guest.id)}
isEditing={guest.isEditing}
editGuest={editedName=> props.editGuest(editedName, guest.id)}
removeGuest={()=> props.removeGuest(guest.id)}
/>)}
</ul>
</div>
The first argument is passed in here
Probably a minor issue that I am too tired to notice lol. Any help is appreciated, thank you for your time!
I think in editGuest it should be guestName: name :)
You are calling the editGuests function with one argument
onChange={e => props.editGuest(e.target.value)}
But it has two arguments in the definition
editGuest=(id, name)=>{
}
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(...) });
);
}
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.
I use React Material-UI for my project. I put many components in render method. Then weird things happened. Some components can not be referred by 'this.refs.REFNAME'. And then I checked 'this.refs' object, it shows as the pic below:
As you can see, only some of the components, which can be referred, are showed on the first line. I don't understand why. Can anyone explain it to me? Thanks lot.
Update: Thanks to James, I get it that the first line should be the status of 'this.refs' at that moment. But still, why some of the components are not in the refs object?
Here's the relevant part of code:
render(){
return <div>
<Table ref='fromform'>
<TableHeader>
<TableRow>
<TableHeaderColumn tooltip='The ID'>No.</TableHeaderColumn>
<TableHeaderColumn tooltip='The Name'>Name</TableHeaderColumn>
<TableHeaderColumn tooltip='The Status'>Notes</TableHeaderColumn>
</TableRow>
</TableHeader>
<TableBody ref='fromformb'>
{playerList.map(function(player, index){
return <TableRow onTouchTap={this._onShowInfo.bind(this, index)} key={'row' + index}>
<TableRowColumn>{index + 1}</TableRowColumn>
<TableRowColumn>{player.name}</TableRowColumn>
<TableRowColumn>{player.notes}</TableRowColumn>
</TableRow>;
}.bind(this))}
</TableBody>
</Table>
<Dialog
title='Player Info'
ref='playerInfoDialog'>
<form role='form' ref='fromfo'>
<div className='form-group' ref='frowwmformb'>
<TextField type='text' hintText='Player Name' ref='txtName' fullWidth={true} />
<TextField type='text' hintText='Notes' ref='txtNotes' fullWidth={true} />
</div>
</form>
</Dialog>
<MainButtonGroup page='players' />
<Spinner />
</div>;
}
_onShowInfo(index){
var player = playerList[index];
console.log(this.refs);
this.refs.playerInfoDialog.show();
this.refs.txtName.setValue(player.name);
this.refs.txtNotes.setValue(player.notes);
}
}
What I want to do with this code is to generate rows in a table with player list, and when I tap a row, a dialog shows up with the data of the row.
But the two 'TextField's with refs of 'txtName' and 'txtNotes' can't be referred(as on the last 2 lines, produce errors). The pic above is produced by 'console.log' in '_onShowInfo' method.
I added some 'ref's with random names just to test.
I found a solution. Change _onShowInfo as below:
_onShowInfo(tid){
var player = playerList[index];
this.refs.playerInfoDialog.show();
setTimeout(function(){
console.log(this.refs);
this.refs.name.setValue(player.name);
this.refs.notes.setValue(player.notes);
}.bind(this), 0);
}
But I don't know why.
This happens because when the dialog is hidden, its children are not mounted yet. So the refs defined in the children are not set yet.
You see them in the console, though, as Chrome automatically updates it when it gets set because there is referential equality between the old this.refs and the new one. If you want to log the refs at a given point in time and make sure it is not updated by Chrome, you could do : console.log({ ...this.refs })
Another approach would be to hold a reference to the selected player in your component's state. Then you would have:
_onShowInfo(tid){
var player = playerList[index];
this.setState({selectedPlayer: player});
}
and
<Dialog
title='Player Info'
open={ !!this.state.selectedPlayer }
>
<form role='form'>
<div className='form-group'>
<TextField type='text' hintText='Player Name' defaultValue={ this.state.player.name} fullWidth={true} />
</div>
</form>
</Dialog>