As part of my role working for AG Grid I get to see a number of repeated issues. One such issue that I have seen multiple times is this.
this.props
isundefined
in a callback but I know that it does have a value in my component!
What is this
?
Let's look at an example of when this can easily occur. Below is a section of the code for a Bar Chart which has added a click event listener.
This example is written in React but you will experience the same in Angular, Typescript and any Class based code.
class ChartExample extends Component {
constructor(props) {
super(props);
this.state = {
options: {
series: [
{
listeners: {
nodeClick: function (event) {
// Do something with our props
console.log("Props are:", this.props)
},
},
},
],
},
};
}
render() {
return (
<div className="wrapper">
<AgChartsReact options={this.state.options} />
</div>
);
}
}
render(<ChartExample test={'Test Value'} />, document.querySelector('#root'));
The key part of this code is the following section where we define our listeners.
listeners:
{
nodeClick: function (event) {
// Do something with our props
console.log("Props are:", this.props)
}
}
If our component receives props
then we would expect that these would be logged to the console. However the above code logs undefined
when a node is clicked. You can confirm this for yourself in this Plunker.
The arrow function fix
If we make what seems like a purely syntax change, swapping out the function definition for the equivalent arrow format, then try again, suddenly our props are now correctly logged to the console! Updated Plunker
listeners: {
nodeClick: (event) => {
// props are defined
console.log("Props are:", this.props)
},
},
So what is going on here and why does using the syntax (event) => {}
suddenly make this.props
have the correct value!?
This binding with arrow function
The short answer to why this is works is that by default arrow functions bind this
to the value in their surrounding scope. So in our example this
will be correctly bound to our component class meaning that this.props
will be correctly defined. When we use a standard function
definition this
is not bound to our component but instead will have the value of the class where our event was fired.
If you want to dive deeper into this topic I would recommend Understanding Arrow Functions in JavaScript
Alternative Fix with bind
It is worth mentioning that you can also fix the original code by explicitly binding this when you define the callback.
listeners: {
nodeClick: function (event) {
console.log("Props are:", this)
}.bind(this),
},
Conclusion
If you ever find yourself surprised that this.props
, or for that matter, this.anyClassProp
is undefined when you are expecting it to contain a certain value then you could check the context in which you are using this
. You may find that simply updating a function definition to an arrow function resolves your issue.
As a result of these issues I have made a large update to all our AG Grid examples so that they use arrow functions by default to help avoid confusion for our users.