While building a form, you will be interacting very heavily with event handlers, and understanding the context of this
property will prevent you from scratching your head down the line when you get error messages such as this is undefined
. Especially in the case of forms, you will be dealing very heavily with event handlers and event handlers will change the context of this
property. Now, if we want to set the new state in our baseline component of the form we created earlier, we will have to call out this.setState
in our validateUsernameOnBlur
function. However, if you try to do that, you are going to hit a this is undefined
error message. For example, take changing our validateUsernameOnBlur
function to the following:
validateUsernameOnBlur(event) {
console.log("I should validate whatever is in ", event.target.value);
this.setState();
}
The preceding code results in the following error:
Figure 2.10: TypeError in the console statement
The reason is that because the event handler code is essentially wrapping up the call and calling our event handler function for us, it's losing the context of the function, which should essentially be the component. Instead, the context becomes undefined. The this is undefined
error message can be hard to track down if you don't know what you are looking for. The error message is ambiguous as this
keyword is not explained.
The good news is that this is an incredibly simple thing to solve. We can explicitly tell JavaScript to bind the context of this
keyword to the component itself instead of allowing the event handler to show us the context. There are two common ways to address this:
- In-line bind statements
- Constructor bind statements
In-line Bind Statements
The simplest method to add a context to our baseline component is to add bind(this)
call to the end of our event handler declaration in the input field like so:
displayForm() {
return (
<div>
Username: <input type="text" onBlur={this.validateUsernameOnBlur.bind(this)} /><br />
Password: <input type="text" /><br />
Password Confirmation: <input type="text" /><br />
Email: <input type="text" /><br />
<br />
<button onClick={this.submitForm}>Submit</button>
</div>
);
}
Make that change and the code will start working again when you select a field other than the username field. This is a shortcut to solve this problem if you only need the bind in a single place, but is not a great strategy if we have to write in-line bind statements multiple times in the code, especially for repeat calls to the same functions. We will see another way to bind this
concept to the component next.
Constructor Bind Statements
We use constructor bind statements to tell JavaScript explicitly that when we reference the this.validateUsernameOnBlur
function in our component, it should always have the context of the component bound to it when this is referenced.
Since the constructor
in the class-based components is used to declare the state of a component, when we are calling this.state()
in the constructor, we should bind our event handlers explicitly inside of our constructor to avoid doing repetitive tasks, especially for the same functions, to save ourselves a little bit of extra time and effort. This requires us to add the following line to the constructor
:
this.validateUsernameOnBlur = this.validateUsernameOnBlur.bind(this);
Note
Constructors have been discussed in detail in Chapter 4, React Lifecycle Methods.
Now we can go back to our displayForm()
function and remove the in-line bind
statement instead:
displayForm() {
return (
<div>
Username: <input type="text" onBlur={this.validateUsernameOnBlur} /><br />
Password: <input type="text" /><br />
Password Confirmation: <input type="text" /><br />
Email: <input type="text" /><br />
<br />
<button onClick={this.submitForm}>Submit</button>
</div>
);
}
Our code will otherwise remain identical. If you try this again, you should again see the focus change work and not result in any additional errors. Let's practice this in the following exercise.
Exercise 2.03: Building our Username Validator
In this exercise, we will put the code we just talked about into our component that we created previously. We will add the bind
statement to the constructor and call our validateUsernameOnBlur
function from our displayForm
function when the form input hits the onBlur
event. To do so, let's go through the following steps:
- Drop our constructor since we can specify the initial state in a different way. Instead, we will use JavaScript fields to define the state by setting a state field on the class:
class App extends Component {
state = {
username: '',
password: '',
passwordConfirmation: '',
email: '',
errors: []
};
validateUsernameOnBlur = this.validateUsernameOnBlur.bind(this);
- Write the
validateUsernameOnBlur()
function. Nothing here should be new:validateUsernameOnBlur(event) {
console.log("I should validate whatever is in ", event.target.value);
this.setState();
}
- Call the
onBlur
event handler inside the displayForm()
function, without needing an in-line bind statement:displayForm() {
return (
<div>
Username: <input type="text" onBlur={this.validateUsernameOnBlur} /><br />
Password: <input type="text" /><br />
Password Confirmation: <input type="text" /><br />
Email: <input type="text" /><br />
<br />
<button onClick={this.submitForm}>Submit</button>
</div>
);
}
- Call the
submitForm
function, which is active while the Submit
button is pressed:submitForm(event) {
console.log("Submitting the form now...");
console.log(event);
}
- Call
displayForm
from the render
method:render() {
return (
<div className="App">
Create Account
<hr />
{this.displayForm()}
</div>
)
}
The resulting class structure looks like the following:
App.js
3 class App extends Component {
4 state = {
5 username: '',
6 password: '',
7 passwordConfirmation: '',
8 email: '',
9 errors: []
The complete code can be found here: https://packt.live/2PsyyMu
The output is as follows:
Figure 2.11: Form component
Another way to define our components is to use some relatively newer syntax (the public field syntax) to define our class component, the properties in the component, and the functions in the component. This allows us to define our functions in such a way that they remember the binding of this
keyword regardless of how they are passed or called via event handlers.
Exercise 2.04: Using Alternative Class Declarations to Avoid Binds
In this exercise, we will use an alternative class declaration so that we can avoid the bind statements altogether. We will use the displayForm
component that we created previously. We will drop the constructor
and we will see how to specify the initial state in a different way using arrow functions and declaring the fields inside it. To do so, let's go through the following steps:
- Drop our
constructor
since we can specify the initial state in a different way. Instead, we will use JavaScript fields to define the state by setting a state field on the class:class App extends Component {
state = {
username: '',
password: '',
passwordConfirmation: '',
email: '',
errors: []
};
}
- Redefine the
validateUsernameOnBlur()
function by changing the function to instead be a field on the class as well. You will need to use the arrow function syntax, mentioned in Chapter 1, Getting Started with React, to make this work:validateUsernameOnBlur = (event) => {
console.log("I should validate whatever is in ", event.target.value);
this.setState();
}
The only major difference here is that we are defining the function in a similar way to how we define other arrow functions. The advantage here is that this function now has this bound appropriately, so we don't need to worry about explicitly binding.
- Call the
onBlur
event handler inside the displayForm()
function:displayForm() {
return (
<div>
Username: <input type="text" onBlur={this.validateUsernameOnBlur} /><br />
Password: <input type="text" /><br />
Password Confirmation: <input type="text" /><br />
Email: <input type="text" /><br />
<br />
<button onClick={this.submitForm}>Submit</button>
</div>
);
}
- Call the
submitForm
function, which is active while the Submit
button is pressed:submitForm(event) {
console.log("Submitting the form now...");
console.log(event);
}
- Call
displayForm
from the render
method:render() {
return (
<div className="App">
Create Account
<hr />
{this.displayForm()}
</div>
)
}
The resulting class structure looks like the following:
App.js
1 class App extends Component {
2 this.state = {
3 username: '',
4 password: '',
5 passwordConfirmation: '',
6 email: '',
7 errors: []
8 };
The complete code can be found here: https://packt.live/2UXLBrr
The output is as follows:
Figure 2.12: Validation in the form component
Unfortunately, this is still a syntax proposal and is not guaranteed to be supported in every environment that you may be working in, so for now, we will stick with the more widely available syntax. If you are working in a Create React App project, though, and feel more comfortable using the proposed fields syntax instead, that remains an option. Create React App will create a project with the appropriate Babel config to use the public class fields syntax by default.
Now let's look at the ways to handle our validation, but we are going to go with an approach that is more aligned with common React best practices.