React class component unusual this binding on methods - javascript

For an example class component like below:
class Todo extends Component {
state = {
list: ["First Todo"],
text: ""
};
handleSubmit(e) {
e.preventDefault();
if (this && this.setState) {
console.log("this present in handleSubmit");
this.setState(prevState => ({
list: prevState.list.concat(this.state.text),
text: ""
}));
} else {
console.log("this not present in handleSubmit");
}
}
handleChange(e) {
if (this && this.setState) {
console.log("this present in handleChange");
this.setState({
text: e.target.value
});
} else {
console.log("this not present in handleChange");
}
}
removeItem(index) {
if (!this || !this.setState) {
console.log("this not present in removeItem");
}
console.log("this present in removeItem");
const list = this.state.list;
list.splice(index, 1);
this.setState({ list });
}
render() {
return (
<div>
<h1>TODO LIST</h1>
<form onSubmit={this.handleSubmit}>
<input value={this.state.text} onChange={e => this.handleChange(e)} />
<button>Add</button>
<ol>
{this.state.list.map((item, index) => {
return (
<li key={index}>
{item}
<button onClick={() => this.removeItem(index)}>Delete</button>
</li>
);
})}
</ol>
</form>
</div>
);
}
}
The behavior of this binding to the class methods is not consistent.
Playing around with the component we will find that, handleChange and removeItem has the correct this context, whereas handleSubmit has this context as undefined.
Both of the function which has correct this context is represented as an arrow function in jsx. Like below:
<input value={this.state.text} onChange={e => this.handleChange(e)} />
While the handleSubmit is passed as function itself. Like below:
<form onSubmit={this.handleSubmit}>
But, I really don't know why this is happening. Because, In my understanding, it should not have mattered how the function was passed i.e. as the function itself or arrow representation as above.

Arrow functions have lexical this. Which means its value is determined by the surrounding scope. So when you use it instead of class methods the this value will be maped to the instance. But when you call this.onSubmit this will be refering to the local scope and not to the instance itself. To solve it either use arrow functions or bind the onSubmit method in your constructor.
constructor(props){
super(props)
this.onSubmit = this.onSubmit.bind(this)
}

In my understanding, it should not have mattered how the function was passed...
So here is new thing to learn
Passing onChange={e => this.handleChange(e)} will be the same as using .bind in the constructor or passing the reference and using .bind.
When passing it as an arrow function in the render method, it will get the this of the component instead of the method's this.
You should notice that onChange={e => this.handleChange(e)} is not a good practice because on every render you will be creating a new function.

Related

React state component - Cannot read property 'state' of undefined

I have the following problem - I can't read value from my state, I get the error:
Cannot read property 'state' of undefined
My class component with a state:
class SideNav extends Component {
state = {
brand: 'Thule'
}
handleToggle = () => this.setState({open: !this.state.open});
handleBrand = (evt) => {
this.setState({brand: evt.target.value});
console.log(this.state.brand); --> WORKS HERE!!!
}
searchProducts() {
console.log(this.state.brand); --> ERROR: cannot read 'state' property of undefined
}
render() {
return (
<div className={classes.sideNav}>
<Button variant={"outlined"} onClick={this.handleToggle} className={classes.sideNavBtn}>Search</Button>
<Drawer
className={classes.drawer}
containerStyle={{top: 55}}
docked={false}
width={200}
open={this.state.open}
onRequestChange={open => this.setState({open})}
>
<AppBar title="Search"/>
<form noValidate autoComplete="off" onSubmit={this.searchProducts}>
<TextField
id="brand"
label="Brand"
margin="normal"
onChange={this.handleBrand}
/>
<Button variant="contained" color="primary" onClick={this.searchProducts}>
Search
</Button>
</form>
</Drawer>
</div>
);
}
}
export default SideNav;
I wonder WHY I'm able to read my this.state.bran value in:
handleBrand = (evt) => {
this.setState({brand: evt.target.value});
console.log(this.state.brand);
}
but NOT in
searchProducts() {
console.log(this.state.brand);
}
I don't understand this case.
searchProducts is a class method either bind it in constructor or use arrow functions (like in handleBrand). Currently your accessing the wrong value for this
searchProducts = () =>{}
Using bind
constructor(){
this.searchProducts = this.searchProducts.bind(this)
}
Arrow functions have lexical this. By using this inside a class method you are accessing the local scope of searchProducts not the Component's instance
this works in confusing ways in JavaScript. It's not working as intended in searchProduct() because you're passing it as a prop to a child component. In your constructor you should bind it to the instance like this:
constructor(props) {
super(props);
this.searchProducts = this.searchProducts.bind(this);
}

React: TypeError: Unable to get property 'handler' of undefined or null reference

I am new to React.js and cannot seem to get any event handlers to work. I get the above-referenced error anytime I make an event handler outside of the render function instead of as an annonymous inner class. Can someone point out what I'm doing wrong please?
class Checkboxes extends Component{
constructor(props) {
super(props);
this.state = { checked: [true, true],
frame: [values, values],
title: ['A', 'B'] };
this.handleChange= this.handleChange.bind(this);
}
handleChange(e) {
let index= e.target.id;
let newState = this.state.checked.slice();
newState[index] = !this.state.checked[index];
this.setState ({checked: newState});
}
render(){
const selectionBoxes = this.state.title.map(function(title, index) {
return (
<div className="w3-checkbox" id={index} onClick={this.handleChange}>
<input
type="checkbox"
label={'Division '+title}
value={this.state.checked[index]}
checked={!this.state.checked[index]}
onChange= {this.handleChange}
id={index} />
<label id={index} onClick={this.handleChange}>
{'Division '+ title}
</label>
</div>
);
});
const frameDisplay = this.state.frame.map(function(frame, index) {
return (
<div>
{this.state.checked[index]} ? null :
<DivFrame frameId={frame} width={this.state.width} height={this.state.height} title={this.state.title[index]} />
</div>
);
});
return (
{selectionBoxes}
);
}
};
export default Checkboxes;
It seems that, even though you may have bound the handleChange to this in the constructor, it is still not in scope within the this.state.title.map(function(title, index) {... function. I have fixed this problem by simply changing this.state.title.map(function(title, index) {... to this.state.title.map((title, index) => {...
This is the ES6 syntax and does the binding for me automatically.
Have a look here for a demo: https://codesandbox.io/s/py2zp8z1kj
Please note that for the sake of simplicity I have removed the code pertaining to the frames component since it's not really related to the problem and to cut down on the work required having to implement that component. To have the event handler working on that component as well though you will have to change the callback of the mapping function to the ES6 syntax as well.
Also, you will notice that in the return of the render() method, I have removed the curly braces because you do not need them since selectionBoxes is raw html and needs not to be evaluated. Actually evaluating it ({ selectionBoxes}) converts the value of selectionBoxes to an object which is NOT a valid react child. So rather put selectionBoxes as is in the render block.
In case you are not able to view the demo, this is how the code looks like:
class Checkboxes extends Component {
constructor(props) {
super(props);
this.state = {
checked: [true, true],
frame: [0, 1],
title: ['A', 'B']
};
}
handleChange(e) {
let index = e.target.id;
let newState = this.state.checked.slice();
newState[index] = !this.state.checked[index];
this.setState({ checked: newState });
}
render() {
const selectionBoxes = this.state.title.map( (title, index) => {
return (
<div className="w3-checkbox" id={index} onClick={this.handleChange}>
<input
type="checkbox"
label={'Division ' + title}
value={this.state.checked[index]}
checked={!this.state.checked[index]}
onChange={this.handleChange}
id={index} />
<label id={index} onClick={this.handleChange}>
{'Division ' + title}
</label>
</div>
);
});
return (
selectionBoxes
);
}
};
export default Checkboxes;

How to correctly bind function with arguments in component?

I've been using React for a few months now, and one of the things I'm finding most difficult is how to properly bind functions that take arguments.
Currently, I have three inputs that share a single update function, but require a different first argument to be passed. Here is my component:
class MyComponent extends Component {
render() {
const { onChange } = this.props;
return(
<div className='my_component'>
<Row>
<Column>
<Input
value={item1}
onChange={ (newValue) => onChange('item1', newValue) } />
</Column>
</Row>
<Row>
<Column>
<Input
value={item2}
onChange={ (newValue) => onChange('item2', newValue) } />
</Column>
</Row>
<Row>
<Column>
<Input
value={item3}
onChange={ (newValue) => onChange('item3', newValue) } />
</Column>
</Row>
</div>
);
}
}
So, currently, I'm using arrow functions in my render function of the component. But through research, I've found that obviously has performance issue in terms of re-rendering.
The solution offered is to bind in the constructor using
constructor() {
super();
this.handleChange = this.handleChange.bind(this)
}
handleChange(event) {
this.props.onChange('ARGUMENT REQUIRED!', event.target.value);
}
The problem is, that I cannot get that first argument to work... Am I supposed to create a function for each and bind one for each in the constructor, like so:
constructor() {
super();
this.handleItem1Change= this.handleItem1Change.bind(this)
this.handleItem2Change= this.handleItem2Change.bind(this)
this.handleItem3Change= this.handleItem3Change.bind(this)
}
handleItem1Change(newValue) {
this.props.onChange('item1', newValue);
}
handleItem2Change(event) {
this.props.onChange('item2', newValue);
}
handleItem3Change(event) {
this.props.onChange('item3', newValue);
}
That seems repetitive...is there a more streamlined way to do this?
If you have control of the Input component, why not have a prop such as name and then in the Input component pass in the onChange function as a prop.
In the Input component whereever you are handling the change you could just do.
<Input
value={item3}
onChange={ onChange }
name='item3'
/>
// in the Input component
handleChange = (value) => {
this.props.onChange(value, this.props.name)
}
and then you would just need to update your onChange to put the value first and the name second. Reason for doing it that way is to ensure this doesn't break your Input component in the other places that it is used since value will still be the first argument, and name is a secondary optional argument.
if you are passing the event back in the onChange instead of value you can still use the event and just do e.target.name as long as you are applying the name prop to the input thats rendered in Input and would look like:
handleChange(event) {
this.props.onChange(event.target.name, event.target.value);
}
You can pass arguments that you want to partially apply right into bind:
constructor() {
super();
this.handleItem1Change = this.props.onChange.bind(this, 'item1');
this.handleItem2Change = this.props.onChange.bind(this, 'item2');
this.handleItem3Change = this.props.onChange.bind(this, 'item3');
}
Alternatively, you can still use arrow functions there:
constructor() {
super();
this.handleItem1Change = newValue => this.props.onChange('item1', newValue);
this.handleItem2Change = newValue => this.props.onChange('item2', newValue);
this.handleItem3Change = newValue => this.props.onChange('item3', newValue);
}
Just add a new layer of abstraction, something like this::
form.js //use the component RowInput
<RowInput name={'item1'} value={'item'} onChange={onChange} />
RowInput.js // stupid/dumb component
export const RowInput = (name, item, onChange)=>
<Column>
<Input
value={item}
onChange={ (val) => onChange(name) } />
</Column>
you could try this to bind the on change callback
<Input value={item1} onChange={onChange.bind(null,'item1') } />
the callback will look like this
onChange(item, event)

How to pass the event argument when binding functions in React?

I have an input HTML tag, where the onChange is currently
onChange={() => { this.props.someFunc(this.props.someVal, e.target.checked) }
However, I want to follow the es-lint no-bind rule (I want to avoid inline functions), and I'm having issues hadling the arguments for this onChange function.
In my constructor I have:
constructor() {
super();
this.state = {
// some state
};
this._onChangeHandler = this._onChangeHandler.bind(this);
}
_this.onChangeHandler = (event, val) => {
this.props.someFunc(event.target.checked, val);
}
render() {
<div>
{
this.props.inputs.map((x) => {
const someValue = // ...a calculated value
return (
<label
}>
<input
onChange={ this._onChangeHandler(someValue) } // need BOTH someValue and the event
checked={ aBool }
type="checkbox"
value={ anotherValue }/>
<span>{ textHere }</span>
</label>
);
})
}
</div>
}
I've taken a look at this post, but no luck so far. What do I need to do to be able to pass both a value and the event to a bound function?
What if you use currying?
// Helper method that returns a function
const generateHandler = (value, method) => e => method(e, value)
// Apply the helper method
<input onChange={generateHandler(someValue, this._onChangeHandler)} />
You can try this:
<input
onChange={(e) => this._onChangeHandler(e, someValue)}
/>
From the es-lint example linked in Fleezey's comment. Here's what it would look like in your case:
var List = React.createClass({
constructor() {
super();
this._onChangeHandler = this._onChangeHandler.bind(this);
}
this._onChangeHandler = (event, val) => {
this.props.someFunc(event.target.checked, val);
}
render() {
<div>
{
this.props.inputs.map((x) => {
const someValue = // ...a calculated value
return (
<label>
<ListItem
onChange={ this._onChangeHandler }
changeHandlerValue={ someValue }
checked={ aBool }
value={ anotherValue } />
<span>{ textHere }</span>
</label>
);
})
}
</div>
}
});
var ListItem = React.createClass({
render() {
// render the input using the props passed in
return (
<input
onChange={this._onChange}
checked={this.props.checked}
type="checkbox"
value={this.props.value}
/>
);
},
_onChange(event) {
// trigger the event handler and pass both the event and the value
this.props.onChange(event, this.props.changeHandlerValue);
}
});
In the accepted currying solution above, the order of the arguments are wrong.
Plus it does not handle multiple args when the handler is actually invoked. Here's an improved version:
// Helper method that returns a function - order matters here!!!
const generateHandler = (value, method) => (...e) => method(value, ...e)
// Apply the helper method
<input onChange={generateHandler(someValue, this._onChangeHandler)} />
As you have your code at the moment, you receive in the event input variable the values of someValue and in the val input variable the event object. That said, you just need to invert the order of your two input variables so you receive what you expect.
When you bind functions to events, your input variables will be called first and then you will get whatever the API of the event is defined to return.

Passing extra argument to onChange Listener in reactjs

I see that an onChange listener usually doesn't have extra arguments other than e.
handleOnChange(e) {
this.setState({email: e.target.value});
}
But is it still possible to pass extra arguments? Like this:
handleOnChange(e,key) {
this.setState({[key]: e.target.value});
}
I modified the code from this thread to make an example
class FormInput extends React.Component{
consturctor(props){
super(props);
this.state = {email:false,password:false}
}
handleOnChange(e,key) {
this.setState({[key]: e.target.value});
}
render() {
return
<form>
<input type="text" name="email" placeholder="Email" onChange={this.handleOnChange('email')} />
<input type="password" name="password" placeholder="Password" onChange={this.handleOnChange('password')}/>
<button type="button" onClick={this.handleLogin}>Zogin</button>
</form>;
}
}
A few ways to do this:
Add an attribute/or access the attribute from the element
Using this code:
class FormInput extends Component{
onChange(e) {
const { target } = e;
const key = target.getAttribute('name');
}
}
Bind the extra attribute (partials) when you create the onChange function
Using this code:
<input name='password' onChange={this.onChange.bind('password')} />
//or
<input name='password' onChange={(e) => this.onChange('password',e)} />
Do note that you would need to change the order of the onChange function
onChange(key,e) {
//key is passed here
}
This is usually not advisable because you would create the function on each render call. See if its fine on your case
List item
Lastly you can wrap the element and from there just pass what the caller needs on the onChange
class Input extends Component {
dispatchOnChange(e) {
const { props } = this;
const { name } = props;
const value = e.target.value;
props.onChange(name,value);
}
render() {
return <input {...this.props} onChange={this.dispatchOnChange}/>
}
}
//your render
<Input type="password" name="password" placeholder="Password" onChange={this.handleOnChange}/>
Hope this helps
You could create an anonymous function, calling handleOnChange with your custom key. That would look like:
<button type="button" onClick={(e) => this.handleLogin(e, index)}>
If you have not worked with anonymous functions before, this is telling JavaScript to create a new function on the fly during render, that takes a parameter e and calls this.handleLogin(e, index). In JavaScript, anonymous functions inherit scope, so the "this" keyword will be scoped correctly.

Categories

Resources