11 min read

React has a unique approach to handling events: declaring event handlers in JSX. The differentiating factor with event handling in React components is that it’s declarative. Contrast this with something like jQuery, where you have to write imperative code that selects the relevant DOM elements and attaches event handler functions to them.

The advantage with the declarative approach to event handlers in JSX markup is that they’re part of the UI structure. Not having to track down the code that assigns event handlers is mentally liberating.

This article is taken from the book React and React Native – Second Edition by Adam Boduch. This book guides you through building applications for web and native mobile platforms with React, JSX, Redux, and GraphQL. To follow along with the examples implemented in this article, you can find the code in the GitHub repository of the book.

In this tutorial, we will look at how event handlers for particular elements are declared in JSX. It will walk you through the implementation of inline and higher-order event handler functions. Then you’ll learn how React actually maps event handlers to DOM elements under the hood. Finally, you’ll learn about the synthetic events that React passes to event handler functions, and how they’re pooled for performance purposes.

Declaring event handlers

In this section, you’ll write a basic event handler, so that you can get a feel for the declarative event handling syntax found in React applications. Then, we will see how to use generic event handler functions.

Declaring handler functions

Let’s take a look at a basic component that declares an event handler for the click event of an element:

Find the code for this section in GitHub.

The event handler function, this.onClick(), is passed to the onClick property of the element. By looking at this markup, it’s clear what code is going to run when the button is clicked.

Multiple event handlers

What I really like about the declarative event handler syntax is that it’s easy to read when there’s more than one handler assigned to an element. Sometimes, for example, there are two or three handlers for an element. Imperative code is difficult to work with for a single event handler, let alone several of them. When an element needs more handlers, it’s just another JSX attribute. This scales well from a code maintainability perspective:

Find the code for this section in GitHub.

This input element could have several more event handlers, and the code would be just as readable.

As you keep adding more event handlers to your components, you’ll notice that a lot of them do the same thing. Next, you’ll learn how to share generic handler functions across components.

Importing generic handlers

Any React application is likely going to share the same event handling logic for different components. For example, in response to a button click, the component should sort a list of items. It’s these types of generic behaviors that belong in their own modules so that several components can share them. Let’s implement a component that uses a generic event handler function:

Find the code for this section on GitHub.

Let’s walk through what’s going on here, starting with the imports. You’re importing a function called reverse(). This is the generic event handler function that you’re using with your element. When it’s clicked, the list should reverse its order.

The onReverseClick method actually calls the generic reverse() function. It is created using bind() to bind the context of the generic function to this component instance.

Finally, looking at the JSX markup, you can see that the onReverseClick() function is used as the handler for the button click.

So how does this work, exactly? Do you have a generic function that somehow changes the state of this component because you bound context to it? Well, pretty much, yes, that’s it. Let’s look at the generic function implementation now:

Find the code for this section on GitHub.

This function depends on a this.state property and an items array within the state. The key is that the state is generic; an application could have many components with an items array in its state.

Here’s what our rendered list looks like:

As expected, clicking the button causes the list to sort, using your generic reverse() event handler:

Next, you’ll learn how to bind the context and the argument values of event handler functions.

Event handler context and parameters

In this section, you’ll learn about React components that bind their event handler contexts and how you can pass data into event handlers. Having the right context is important for React event handler functions because they usually need access to component properties or state. Being able to parameterize event handlers is also important because they don’t pull data out of DOM elements.

Getting component data

In this section, you’ll learn about scenarios where the handler needs access to component properties, as well as argument values. You’ll render a custom list component that has a click event handler for each item in the list. The component is passed an array of values as follows:

Find the code for this section on GitHub.

Each item in the list has an id property, used to identify the item. You’ll need to be able to access this ID when the item is clicked in the UI so that the event handler can work with the item. Here’s what the MyList component implementation looks like:

Find the code for this section on GitHub.

Here is what the rendered list looks like:

You have to bind the event handler context, which is done in the constructor. If you look at the onClick() event handler, you can see that it needs access to the component so that it can look up the clicked item in this.props.items. Also, the onClick() handler is expecting an id parameter. If you take a look at the JSX content of this component, you can see that calling bind() supplies the argument value for each item in the list. This means that when the handler is called in response to a click event, the id of the item is already provided.

Higher-order event handlers

A higher-order function is a function that returns a new function. Sometimes, higher-order functions take functions as arguments too. In the preceding example, you used bind() to bind the context and argument values of your event handler functions. Higher-order functions that return event handler functions are another technique. The main advantage of this technique is that you don’t call bind() several times. Instead, you just call the function where you want to bind parameters to the function. Let’s look at an example component:

Find the code for this section on GitHub.

This component renders three buttons and has three pieces of state—a counter for each button. The onClick() function is automatically bound to the component context because it’s defined as an arrow function. It takes a name argument and returns a new function. The function that is returned uses this name value when called. It uses computed property syntax (variables inside []) to increment the state value for the given name. Here’s what that component content looks like after each button has been clicked a few times:

Inline event handlers

The typical approach to assigning handler functions to JSX properties is to use a named function. However, sometimes you might want to use an inline function. This is done by assigning an arrow function directly to the event property in the JSX markup:

Find the code for this section on GitHub.

The main use of inlining event handlers like this is when you have a static parameter value that you want to pass to another function. In this example, you’re calling console.log() with the string clicked. You could have set up a special function for this purpose outside of the JSX markup by creating a new function using bind(), or by using a higher-order function. But then you would have to think of yet another name for yet another function. Inlining is just easier sometimes.

Binding handlers to elements

When you assign an event handler function to an element in JSX, React doesn’t actually attach an event listener to the underlying DOM element. Instead, it adds the function to an internal mapping of functions. There’s a single event listener on the document for the page. As events bubble up through the DOM tree to the document, the React handler checks to see whether any components have matching handlers. The process is illustrated here:

Why does React go to all of this trouble, you might ask? To keep the declarative UI structures separated from the DOM as much as possible.

For example, when a new component is rendered, its event handler functions are simply added to the internal mapping maintained by React. When an event is triggered and it hits the document object, React maps the event to the handlers. If a match is found, it calls the handler. Finally, when the React component is removed, the handler is simply removed from the list of handlers.

None of these DOM operations actually touch the DOM. It’s all abstracted by a single event listener. This is good for performance and the overall architecture (keep the render target separate from the application code).

Synthetic event objects

When you attach an event handler function to a DOM element using the native addEventListener() function, the callback will get an event argument passed to it. Event handler functions in React are also passed an event argument, but it’s not the standard Event instance. It’s called SyntheticEvent, and it’s a simple wrapper for native event instances.

Synthetic events serve two purposes in React:

  • Provides a consistent event interface, normalizing browser inconsistencies
  • Synthetic events contain information that’s necessary for propagation to work

Here’s an illustration of the synthetic event in the context of a React component:

Event pooling

One challenge with wrapping native event instances is that this can cause performance issues. Every synthetic event wrapper that’s created will also need to be garbage collected at some point, which can be expensive in terms of CPU time.

For example, if your application only handles a few events, this wouldn’t matter much. But even by modest standards, applications respond to many events, even if the handlers don’t actually do anything with them. This is problematic if React constantly has to allocate new synthetic event instances.

React deals with this problem by allocating a synthetic instance pool. Whenever an event is triggered, it takes an instance from the pool and populates its properties. When the event handler has finished running, the synthetic event instance is released back into the pool, as shown here:

This prevents the garbage collector from running frequently when a lot of events are triggered. The pool keeps a reference to the synthetic event instances, so they’re never eligible for garbage collection. React never has to allocate new instances either.

However, there is one gotcha that you need to be aware of. It involves accessing the synthetic event instances from asynchronous code in your event handlers. This is an issue because, as soon as the handler has finished running, the instance goes back into the pool. When it goes back into the pool, all of its properties are cleared. Here’s an example that shows how this can go wrong:

Find the code for this section on GitHub.

The second call to  console.log() is attempting to access a synthetic event property from an asynchronous callback that doesn’t run until the event handler completes, which causes the event to empty its properties. This results in a warning and an undefined value.

This tutorial introduced you to event handling in React. The key differentiator between React and other approaches to event handling is that handlers are declared in JSX markup. This makes tracking down which elements handle which events much simpler.

We learned that it’s a good idea to share event handling functions that handle generic behavior.  We saw the various ways to bind the event handler function context and parameter values. Then, we discussed the inline event handler functions and their potential use, as well as how React actually binds a single DOM event handler to the document object. Synthetic events are an abstraction that wraps the native event, and you learned why they’re necessary and how they’re pooled for efficient memory consumption.

If you found this post useful, do check out the book, React and React Native – Second Edition. This book guides you through building applications for web and native mobile platforms with React, JSX, Redux, and GraphQL.

Read Next

JavaScript mobile frameworks comparison: React Native vs Ionic vs NativeScript

React introduces Hooks, a JavaScript function to allow using React without classes

React 16.6.0 releases with a new way of code splitting, and more!

Subscribe to the weekly Packt Hub newsletter. We'll send you the results of our AI Now Survey, featuring data and insights from across the tech landscape.