ref – https://www.youtube.com/watch?v=Ke90Tje7VS0&t=0s&list=LLJEwHxS-Vsf-N8xSFHeMirw&index=4
Starts at 1:01:06 of the video
Handling Events
All those standard DOM events are available via the JSX.
We’ll use onClick and have it respond to our function handleIncrement.
However, when we execute the function, we notice that the this object is undefined.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
handleIncrement() { // this is undefined in this method console.log("increment clicked" + this.state.count ); } render() { return ( <div> <button onClick={this.handleIncrement} className="btn btn-secondary"> click me </button> </div> ); } |
Its because we need to bind the event handler.
But why is it that in an event handler function, the this object is undefined?
Depending on how a function is called, the ‘this’ changes.
1 |
obj.method(); |
The this reference will always return the calling object.
1 |
function(); |
However, if the function is called as standalone, it will return window object.
If the standalone function is called within ‘strict mode’, it will return undefined.
And that is the reason, our event handler function does not have access to ‘this’.
Notice that handleIncrement is a REFERENCE. We didn’t execute the function yet. If the function was executed right away, then it will correctly call the function, and the function will be able to display this.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
// gets passed as a reference handleIncrement() { // 'this' is undefined console.log("increment clicked" + this.state.count); } // gets executed right away names() { console.log(this); // this is valid return "btn btn-secondary"; } render() { return ( <div> <button onClick={this.handleIncrement} className={this.names()}> click me </button> </div> ); } |
But for event handlers, since we pass the handleIncrement function reference, when it is used, it will be used in another function of a different environment. It will not be able have class Counter as its surrounding environment. It will be used in its own environment, namely, as a standalone function. Hence standalone functions (in a ‘strict mode’) will have undefined as the this object.
use ‘bind’ in the constructor
We implement a constructor function. Make sure you call super to initiate its parent class.
Then we bind the current this object to that function. So that when the reference of that function gets passed around, the this will always return to this object, and subsequently, the properties in this classCounter.
Use custom constructor to bind your event handlers to class Counter
1 2 3 4 5 6 7 8 9 10 11 12 |
constructor() { super(); // Use reference to function handleIncrement and // call bind so that the function binds to class Counter // take property handleIncrement and references // the newly bound handleIncrement reference this.handleIncrement = this.handleIncrement.bind(this); // hence, the this object in handleIncrement // function will always be referencing the class Counter object. } |
Now, this.state.count will be valid.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
handleIncrement() { console.log("increment clicked" + this.state.count); } render() { return ( <div> <button onClick={this.handleIncrement} className={this.names()}> click me </button> </div> ); } |
The reasoning for using bind is explained here
Sometimes we need to pass in objects to our event handlers. In order to do this without having to execute the event handler via syntax, we define an anonymous function. That way, the event handler only executes when you click:
Pass in objects to handle event handlers like so:
1 2 3 |
onClick={ () => this.handleIncrement({id: 1}) } |
or when you’re trying to loop through something and pass in an object to event handlers:
1 2 3 |
onClick={ () => this.handleIncrement(product) } |
Updating the State
In react, we DO NOT modify the state directly. Instead we use setState, which is a method inherited from the base class Component.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
handleIncrement() { // this is undefined in this method console.log("increment clicked" + this); // DO NOT DO THIS: React not aware of this // this.state.count++; // DO THIS this.setState({ count: this.state.count + 1 }); // we have to explicitly tell React we're updating the state. // we pass in the object with the property we want to update. // set its value to what we want } |
We’re telling React we’re updating the state. It will then diff it with the previous virtual DOM. Finally, it applies the changes to the real DOM.
full code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
import React, { Component } from "react"; class Counter extends Component { // state is a special property in react component state = { count: 0, tags: [] }; // define property styles = { fontSize: 30, fontWeight: "bold" }; constructor() { super(); this.handleIncrement = this.handleIncrement.bind(this); // hence, the this object in handleIncrement // function will always be referencing the class Counter object. } renderTags() { if (this.state.tags.length === 0) return <p>There are no tags</p>; return ( <ul> {this.state.tags.map(function(tag) { return <li key={tag}>{tag}</li>; })} </ul> ); } handleIncrement() { // this is undefined in this method console.log("increment clicked" + this); //this.state.count++; // React not aware of this // we have to explicity tell React we're updating the state. this.setState({ count: this.state.count + 1 }); } names() { console.log(this); return "btn btn-secondary"; } getBadgeClasses() { let classes = "badge m-2 badge-"; classes += this.state.count === 0 ? "warning" : "primary"; return classes; } formatCount() { return this.state.count === 0 ? "Zero" : this.state.count; } render() { return ( <div> <span className={this.getBadgeClasses()}>{this.formatCount()}</span> <button onClick={this.handleIncrement} className={this.names()}> click me </button> </div> ); } } export default Counter; |