There are so many articles about how to handle events in React components. This is because of issues with this . Maintaining the proper context of this in component functions requires either using the bind method or arrow functions. This is because of the very nature of inline events. Because the event is defined on a DOM node, it has no direct reference to the parent component.

There is a third way to deal with events, one that most JavaScript developers hardly know, using the handleEvent interface. This has been supported since version 1 of Chrome, Firefox and Safari. IE did not gain support for it until version 9. Currently, all modern browsers support handleEvent . So, what exactly is it and what does it do?

handleEvent

According to Mozilla Developer Network:

The EventListener method handleEvent() is called by the user agent when an event is sent to the EventListener , in order to handle events that occur on an observed EventTarget .

This means that if what is passed to an event listener has a handleEvent method, that will be used. Instinctively we pass callbacks to event listeners to handle the events. Alternatively, you could pass an object with a handleEvent method:

const eventObj = {

handleEvent(e) {

alert(`Hello, there! You clicked on a ${e.target.nodeName}`)

}

} const btn = document.querySelector('button')

btn.addEventListener('click', eventObj)

Notice how in the above example, we have access to the event through the first parameter of the handleEvent method. Using this pattern, we could create an object with methods for handling a component’s state. In fact, we will do this with a React component.

React Component + ref + handleEvent

We are now going to create a React counter that can increase and decrease its value by click on its two buttons. We will do this using the handleEvent interface inside a React component. It’ll look like this when done:

To be able to use handleEvent with a React component like we did in the previous simple example, we first need to have an element to register an event listener on. This will require ref and componentDidMount . Since this is a counter, we will start of with a value of 0. And we’ll create a ref called “element.”

class Counter extends Component {

constructor(props) {

super(props)

this.state = {num: 0}

this.element = React.createRef()

}

}

Next we’ll define the structure of the counter in the component’s render function. Notice that we are not using any inline events. We set our ref value on the root element of the component, the div tag, with ref={this.element} :

class Counter extends Component {

constructor(props) {

super(props)

this.state = {num: 0}

this.element = React.createRef()

}

render() {

return (

<div ref={this.element}>

<button className='increase'>+</button>

<label>{this.state.num}</label>

<button className="decrease">-</button>

</div>

)

}

}

We can now reference the base element of the component with this.element . We’ll do that in the componentDidMount lifecycle hook. We’ll use the element reference to register a click event. Instead of defining a callback, we’ll just pass in the class itself as the object, using the this keyword. Examine the componentDidMount lifecycle hook below:

class Counter extends Component {

constructor(props) {

super(props)

this.state = {num: 0}

this.element = React.createRef()

}

render() {

return (

<div ref={this.element}>

<button className='increase'>+</button>

<label>{this.state.num}</label>

<button className="decrease">-</button>

</div>

)

}

componentDidMount() {

this.element.current.addEventListener('click', this)

}

}

This means that when the user clicks on anything in side the component, the browser will search the class for a handleEvent method. We therefore need to provide our class with one. That method will receive one argument, the event.

Since our component has two buttons, we can use their classes to identify which button the user clicked. If the class is increase , we increase the counter state, if it’s decrease , we decrease its value. For this we’ll create one method:

updateCounter(value) {

this.setState(prevState => {

prevState.num += value

return prevState

})

}

We can them pass a positive or negative value to update the component’s state. The handleEvent method will look like this:

handleEvent(e) {

e.target.className === 'increase' && this.updateCounter(+1)

e.target.className === 'decrease' && this.updateCounter(-1)

}

And here’s the complete component:

class Counter extends Component {

constructor(props) {

super(props)

this.state = {num: 0}

this.element = React.createRef()

}

render() {

return (

<div ref={this.element}>

<button className='increase'>+</button>

<label>{this.state.num}</label>

<button className="decrease">-</button>

</div>

)

}

componentDidMount() {

this.element.current.addEventListener('click', this)

}

handleEvent(e) {

e.target.className === 'increase' && this.updateCounter(+1)

e.target.className === 'decrease' && this.updateCounter(-1)

}

updateCounter(value) {

this.setState(prevState => {

prevState.num += value

return prevState

})

}

} ReactDOM.render(<Counter />, document.querySelector('article'))

This pattern separates events out of the component markup and into JavaScript. No need to search through markup for inline events. And no need to worry about the context of events. The context will always be the component instance. As a plus, this is very memory efficient and, because there are no callbacks, memory leaks from closures are avoided. Removing the event is dead simple. We just pass in the component itself as this :

this.element.current.removeEventListener('click', this)

And here it is live on Codepen:

Summary

Implementing handleEvent with a React component is straightforward and offers some substantial benefits over inline events. That said, our example was very simple. If the parts of your component are deeply nested in the component tree, you will want to register the event handler for them closer to where they need to be captured. That’s because with event delegation the event has to bubble up from where the event occurs to were it is captured.