Event Handling in React
In this section, we will briefly discuss how to create event handlers and explore some nuances related to classes in React.
React employs a cross-platform wrapper, called Synthetic Events, to handle almost all native browser events. As a result, using addEventListener is usually unnecessary, except for very specific situations.
Let's begin by examining how to add an event handler to a functional component, using the example of ExampleEvents.jsx. The commonly used onClick event will serve as our starting point.
To create an event handler, we first need to create a simple function. In the following example, arrow functions are utilized. Two functions, handleClick and handleHover, are defined, and inside each of them, we call the alert method, passing the argument "Hello onClick" in the first case and "Hello Hover" in the second. Both functions are devoid of complex logic.
It is important to note that we will eventually return to our primary application after this brief overview of event handling. We don't want our audience to become too disengaged.
import React from 'react'
import classes from './ExampleEvents.module.scss'
const ExampleEvents = () => {
const handleClick = () => {
alert('Hello onClick!')
}
const handleHover = () => {
alert('Hello Hover!')
}
return (
<div className={classes.root}>
<button
type="button"
className="btn btn-primary mr-2"
onClick={handleClick}
>
onClick
</button>
<button
type="button"
className="btn btn-warning mr-2 "
onMouseEnter={handleHover}
>
onHover
</button>
</div>
)
}
export { ExampleEvents }
We are already familiar with props, so let's focus on the first button, which has the props onClick, and the second button, which has onMouseEnter. It may seem like we are not importing or receiving them in the component?
React performs a lot of magic under the hood to ensure that events work consistently across all browsers. If we use our editor's autocomplete feature, we can see a huge list of integrated events. For a complete list of events, you can refer to the official technical documentation.
Through this simple example, we have observed that creating and handling events in React is not as complex as it may seem. We choose the desired event from the list, create a handler function, and pass it to our onClick, and so on.
Classes
However, there are some nuances when working with classes in React, and we will now explore them through an example. We will refactor our functional component into a class component and rewrite the handler functions as class methods. Aere repetiti cognataque natus. Habebat vela solutis saepe munus nondum adhuc oscula nomina pignora corpus deserat.
Let's make our example a bit more complex by adding state and passing the 'name' property as the second argument to alert.
import React, { Component } from 'react'
import classes from './ExampleEvents.module.scss'
class ExampleEvents extends Component {
handleClick() {
alert('Hello onClick!')
}
handleHover() {
alert('Hello Hover!')
}
render() {
return (
<div className={classes.root}>
<button
type="button"
className="btn btn-primary mr-2"
onClick={this.handleClick}
on
>
onClick
</button>
<button
type="button"
className="btn btn-warning mr-2 "
onMouseEnter={this.handleHover}
>
onHover
</button>
</div>
)
}
}
export { ExampleEvents }
To further complicate our example, let's add state and pass the 'name' property as the second argument to alert.
import React, { Component } from 'react'
import classes from './ExampleEvents.module.scss'
class ExampleEvents extends Component {
state = {
name: 'Vasya',
}
handleClick() {
const { name } = this.state
alert('Hello onClick!', name)
}
handleHover() {
alert('Hello Hover!')
}
render() {
return (
<div className={classes.root}>
<button
type="button"
className="btn btn-primary mr-2"
onClick={this.handleClick}
on
>
onClick
</button>
<button
type="button"
className="btn btn-warning mr-2 "
onMouseEnter={this.handleHover}
>
onHover
</button>
</div>
)
}
}
export { ExampleEvents }
[IMAGE PLACEHOLDER]
Oops... our application has broken down.
Problem with THIS
As expected, we get an error. Advanced users know what the problem is - it's all about this and where it points. There are several ways to solve this problem, and I'll list the main ones you may encounter during development.
Class Properties
If you are using create-react-app like me, the easiest way to solve this problem is to use class properties and declare methods using arrow functions. This feature is provided to us by the @babel/plugin-proposal-class-properties plugin.
To solve the "this" problem in a custom Webpack configuration, you can install and configure the babel plugin "@babel/plugin-proposal-class-properties". Here are the steps:
npm install --save-dev @babel/plugin-proposal-class-properties
{
"presets": [...],
"plugins": [
"@babel/plugin-proposal-class-properties"
]
}
Note that we're using the arrow function syntax here, which automatically binds "this" to the component instance.
import React, { Component } from 'react'
import classes from './ExampleEvents.module.scss'
class ExampleEvents extends Component {
state = {
name: 'Vasya',
}
handleClick = () => {
const { name } = this.state
alert(`Hello onClick ${name}`)
}
handleHover = () => {
alert('Hello Hover!')
}
render() {
return (
<div className={classes.root}>
<button
type="button"
className="btn btn-primary mr-2"
onClick={this.handleClick}
on
>
onClick
</button>
<button
type="button"
className="btn btn-warning mr-2 "
onMouseEnter={this.handleHover}
>
onHover
</button>
</div>
)
}
}
export { ExampleEvents }
Bind This
The second option, and the most common one in older projects, is also considered a canonical example if you are taking the Freecodecamp course in 2020. The essence is that we create state through the constructor and call the bind(this) function, so that these functions will have a common this. It looks like this:
import React, { Component } from 'react';
import classes from './ExampleEvents.module.scss';
class ExampleEvents extends Component {
constructor(props) {
super(props);
this.state = {
name: 'Vasya',
};
this.handleClick = this.handleClick.bind(this);
this.handleHover = this.handleHover.bind(this);
}
handleClick() {
const { name } = this.state;
alert(`Hello onClick ${name}`);
}
handleHover() {
alert('Hello Hover!');
}
render() {
return (
<div className={classes.root}>
<button
type="button"
className="btn btn-primary mr-2"
onClick={this.handleClick}
on
>
onClick
</button>
<button
type="button"
className="btn btn-warning mr-2 "
onMouseEnter={this.handleHover}
>
onHover
</button>
</div>
);
}
}
export { ExampleEvents };
As you can see, this approach is very verbose. You may say, "But there is an npm package called auto-bind that does everything for us!" But let's agree that writing a method as an arrow function is much faster and requires no installation.
Note: there is another option when doing bind(this) directly in the markup, which is probably one of the worst solutions to the problem. It is better to avoid binding during rendering, as a new function is returned with every rendering, and on top of that it is very easy to make a mistake and lose readability.
Handlers in render()
Moving methods into the render() method and rewriting them as arrow functions is not a good solution. Each time the component is re-rendered, a new instance of the function will be created, which can cause performance issues. Additionally, putting methods in the render() method can make the code harder to read and understand, especially if the component has many methods. Therefore, it's generally recommended to avoid this approach.
import React, { Component } from 'react';
import classes from './ExampleEvents.module.scss';
class ExampleEvents extends Component {
state = {
name: 'Vasya',
};
render() {
const handleClick = () => {
const { name } = this.state;
alert(`Hello onClick ${name}`);
};
const handleHover = () => {
alert('Hello Hover!');
};
return (
<div className={classes.root}>
<button
type="button"
className="btn btn-primary mr-2"
onClick={handleClick}
on
>
onClick
</button>
<button
type="button"
className="btn btn-warning mr-2 "
onMouseEnter={handleHover}
>
onHover
</button>
</div>
);
}
}
export { ExampleEvents };
We moved the event handlers to the render method, because arrow functions do not have their own "this" keyword, we get exactly the behavior we expected. Whether this method is worse or better, it's not for me to judge.
But as you understand, these event handlers will be recreated on every render, which is not very good for performance.
In legacy projects, I used the first option, as it is the closest to me and I often used it in class-based development.
Summary
You may be wondering why I showed you three ways to work around the problem if we'll still end up using the first one anyway. The thing is, these solutions have been used in the React ecosystem for a long time and you should know about them. When applying for a job as a React developer, you may be asked about them in an interview or asked to write a solution using one of these methods.
To summarize:
- To create an event, it is enough to pass props with the necessary event. The link to the full list of events is here.
- Remember this in class components and use class properties.
- We can attach multiple handlers to a React element, for example, use onClick and onMouseEnter.
- Create handler functions using arrow functions to avoid problems related to this.
In the next article, we'll return to our ToDoApp and implement removing a task from the list.