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);
}
Related
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.
I am sort of new to React and just started reading the Road to learn React free ebook. Anyway there is a part in the book where it says that in order to access this inside a class method, we need to bind class method to this. An example that is provided there clearly shows that:
class ExplainBindingsComponent extends Component {
onClickMe() {
console.log(this);
}
render() {
return (
<button
onClick={this.onClickMe}
type="button"
>
Click Me
</button>
);
}
}
when the button is clicked, this is undefined, and if I add a constructor with this:
constructor() {
super();
this.onClickMe = this.onClickMe.bind(this);
}
I can access this inside a method. However, I am now confused because I'm looking at an example where there's no binding and this is accessible:
class App extends Component {
constructor(props) {
super(props);
this.state = {
list,
};
}
onDismiss(id) {
console.log(this);
const updatedList = this.state.list.filter(item => item.objectID !== id);
this.setState({list: updatedList});
}
render() {
return (
<div className="App">
{this.state.list.map(item =>
<div key={item.objectID}>
<span>
<a href={item.url}>{item.title}</a>
</span>
<span>{item.author}</span>
<span>{item.num_comments}</span>
<span>{item.points}</span>
<span>
<button
onClick={() => this.onDismiss(item.objectID)}
type="button"
>
Dismiss
</button>
</span>
</div>
)}
</div>
);
}
}
Inside onDismiss I can print this without any problems, although I didn't bind it ? How is this different from the first example ?
Cause of these four characters:
() =>
Thats an arrow function. Unlike regular functions, they don't have their own context (aka this) but rather take the one of their parent (render() in this case) and that has the right context.
When you declare as a function using () => it automatically binds itself to this.
Take a look here:
https://hackernoon.com/javascript-es6-arrow-functions-and-lexical-this-f2a3e2a5e8c4
I have a render within a component that looks like this:
render() {
if (!this.props.authorities) {
return <div>Loading...</div>
}
return (
<div>
<Col xsOffset={0.5} md={2}>
<ButtonGroup Col xsOffset={1} md={4} justified centered>
<DropdownButton title="Authority" id="bg-justified-dropdown">
{this.props.authorities.map(this.renderAuthority)}
</DropdownButton>
</ButtonGroup>
</Col>
</div>
)
}
}
It renders a list of dropdown items using the renderAuthority function, which looks like this:
renderAuthority(authorityData) {
return (
<MenuItem onClick={this.clickAuthority(authorityData.LocalAuthorityId)} key={authorityData.LocalAuthorityId} eventKey={authorityData.LocalAuthorityId}>{authorityData.Name}</MenuItem>
)
}
The onClick within there calls a function called clickAuthority, which looks like this:
clickAuthority(data) {
this.props.fetchRatings(data)
}
My constructor also looks like this:
constructor(props) {
super(props);
this.clickAuthority = this.clickAuthority.bind(this);
}
However, I get an error with the following:
Unhandled Rejection (TypeError): Cannot read property 'clickAuthority' of undefined
This references the MenuItem onClick. Any idea what I'm doing wrong and how I might fix it?
By default, the .map() function bind the callback with global environnement object. So binds your this.renderAuthority function with this in your constructor too.
constructor(props) {
super(props);
this.renderAuthority = this.renderAuthority.bind(this);
this.clickAuthority = this.clickAuthority.bind(this);
}
Secondly, when you are writing that:
onClick={this.clickAuthority(authorityData.LocalAuthorityId)}
You are instantly calling the function and giving its return to the onClick property. You have to make this:
onClick={() => this.clickAuthority(authorityData.LocalAuthorityId)}
I ran into a weird bug trying to use refs.
Parent component:
class Parent extends Component {
constructor(props) {
super(props);
this.state = {displayPage: 'one'};
this.changePage = this.changePage.bind(this);
}
changePage(str){
this.setState({
displayPage: str
})
}
render(){
return(
<div onClick={ () => this.displayPage('one')}>One</div>
<div onClick={ () => this.displayPage('two')}>Two</div>
<div onClick={ () => this.displayPage('three')}>Three</div>
{this.state.displayPage === 'one' ? <One /> : true}
{this.state.displayPage === 'two' ? <Two /> : true}
{this.state.displayPage === 'three' ? <Three /> : true}
);
}
}
Now, just a simple example of a child component:
class Two extends Component {
render(){
console.log(this, this.refs)
return(
<div refs="test">This is the Two component</div>
);
}
}
My problem is that the console.log for "this" will show a property of "refs" that has everything I want. When it logs "this.refs" all I get back is "Object {}". This will only happen in components Two and Three, or basically, the components that aren't immediately displayed because of the state. "this.refs" will always work for the component immediately displayed.
Also, if I took them out of the ternary condition then refs will work as intended.
change refs in the div to ref like such:
ref="test"
also, just assigning refs by string is getting deprecated, so I suggest you just pass in a callback to a ref that reassigns it as a static property to the class like this:
ref={(node) => { this.test = node; }}
I'm struggling to understand where I can use props passed by parent's component. It seems that the props are available only in render() method.
The piece of code below is working perfectly but I can't easily serialize the form data and do "e.preventDefault()" thing (can I?)...it'd be better written in const Form = props => { ... })
class Form extends Component {
render() {
const {
handleSubmit
} = this.props;
return (
<div>
<form
onSubmit={this.props.handleSubmit.bind(this)}
>
<TextInput />
<button className="Button">Add</button>
</form>
</div>
);
}
}
BUT this does not work (props are not available in onSubmit method):
class Form extends Component {
onSubmit(e) {
e.preventDefault();
const data = ... serialized form data;
this.props.handleSubmit(data);
}
render() {
return (
<div>
<form
onSubmit={this.onSubmit}
>
<TextInput />
<button className="Button">Add</button>
</form>
</div>
);
}
}
Am I misunderstanding some react.js approach? Is there possibly some .bind(this) missing?
Thanks in advance.
Is there possibly some .bind(this) missing?
Yes you need/can use .bind, or use arrow functions, because now this does not refer to Form
class Form extends Component {
constructor() {
super();
this.onSubmit = this.onSubmit.bind(this)
}
...
}
or just use arrow function
<form
onSubmit={ (e) => this.onSubmit(e) }
>