React Events

Chapter 15 27 mins

Learning outcomes:

  1. The synthetic event system of React
  2. Event-handler props in React
  3. this in React's event handlers
  4. The nativeEvent property
  5. Handler functions are closures
  6. Event handlers and side effects

Introduction

React is a JavaScript library meant to create user interfaces. And user interfaces aren't static, are they? Interaction sits right at the heart of all UIs. And, as we know, this interaction is powered by events.

You would already be aware of events from JavaScript, so we won't be discussing those here. If you feel that your knowledge of events has gone a little bit rusty over time, you can refresh it in the JavaScript Events unit of our comprehensive JavaScript course.

In this chapter, we shall discover the synthetic events ecosystem used by React and the purpose of using it instead of the native DOM event system directly. Next, we'll consider event-handler props in React and their differing nomenclature from that of DOM event-handler properties.

We'll also consider the nativeEvent property of React events and see what purpose it serves. And between all of this, we'll consider a good handful of examples.

So let's get started.

Synthetic events in React

When we develop React apps, we usually work with synthetic events.

Alright, but what does this mean?

By definition, the term 'synthetic event' simply refers to a customly-generated event, i.e. one not generated by the user agent itself; hence the term 'synthetic'.

As stated in React's official docs, this whole system of synthetic events is based upon the standard DOM event system, as defined in the WHATWG DOM standard.

As you may already know, there are numerous instances where DOM events have implementation inconsistencies across different browsers.

In one browser, a given property of an event object might have a certain name; in another browser, that property might exist but have a different name, or maybe a different behavior, or just might not exist at all. Some browsers fire certain events, some don't.

For normalizing most, if not all, of these inconsistencies, React decided to create its own event system, acting on top of native DOM events.

What this means is that we can expect a synthetic mouse event to have the same properties/methods that are otherwise available on a native mouse event. Similarly, we can expect a synthetic clipboard event to have the same properties/methods that are otherwise available on a native clipboard event.

This applies to all kinds of events that we know of in JavaScript.

If you look into the source code of React DOM, you'll literally be astounded by the amount of polyfilling and normalization done by the library to give us a consistent event system to work with.

React events are kind of like a wrapper over DOM events. And stating it once again, the purpose of this wrapping is to normalize inconsistencies in the implementation of events across different browsers.

Obviously, this can't be possible if React doesn't handle native DOM events. In fact, React essentially handles almost all DOM events. Once a particular event fires, React creates a synthetic event out of it and then dispatches this on the respective React element.

The whole idea isn't really any complex to understand.

Won't synthetic events be bad performance-wise?

One thing that comes to the mind after this discussion is that since React puts an additional layer between an interaction and the underlying native DOM events, it might lead to a poor performance on frequently-firing events, like mousemove.

Well, React has been designed in a way that it always runs at the top-most speed so that we don't have to worry about such concerns.

It has been thoroughly benchmarked and tested on numerous occasions and, likewise, we can safely claim that this whole system of synthetic events in React is performant and efficient.

With synthetic events and the idea behind them understood, let's now explore the way to handle events in React, i.e. using event-handler props.

Event-handler props

Do you recall event-handler properties from the DOM? Let's take a quick example.

Suppose we have the following HTML element:

<h1>A clickable heading</h1>

and want to handle the click element on it.

Using the onclick event-handler property of this element node in the DOM, we can very conveniently do so, as shown below:

let h1Element = document.querySelector('h1');

h1Element.onclick = function() {
   alert('Heading clicked.');
}

The moment the element is clicked, the onclick handler is invoked and gets a MouseEvent object as an argument representing the fired click..

Live Example

It's all pretty basic stuff.

Now in React, handling the click event is almost the same. Almost.

First, let's create the <h1> React element using JSX, and our App component:

function App() {
   return (
      <h1>A clickable heading</h1>
   );
}

Now, let's set an onClick attribute (which is just a prop) on this element and assign it a function to make the desired alert:

function App() {
   return (
      <h1 onClick={() => alert('Heading clicked')}>A clickable heading</h1>
   );
}

This was pretty simple.

Live Example

Here we've used an anonymous function as the value of the onClick attribute, because the body of the function was just a simple alert(). But when this ceases to be the case, and the function's body starts to become complex enough, it's desirable to shift to a named function.

For example, following we rewrite the code above using a named function as the value of onClick:

function App() {
   function onClick() {
      alert('Heading clicked.');
   }

   return (
      <h1 onClick={onClick}>A clickable heading</h1>
   );
}

As you can see, we didn't really improvise in the function's name — just called it onClick(). There's absolutely nothing wrong in this.

The naming of event handler functions might seem like an easy task, and it indeed is, but only as long as the code base remains simplistic. As the code base grows larger and larger, with tons and tons of places where event handlers are used, it becomes worthwhile having a nice plan to name these functions rather than having to guess them haphazardly.

In this regard, for a particular handler function, we can either base its name upon the underlying DOM event that it handles or upon the action that it performs in the app.

For instance, if a button's click serves to increment a counter, we could call its handler function onClick, or onClickHandler; or maybe even onIncrement, or onIncrementHandler. There's no right or wrong here; it entirely depends on the current scenario at hand, and the preference of the developer.

Whatever naming convention you use, make sure to remain consistent with it. Consistency is crucial in programming.

As you'll start developing more and more React applications, you'll get much better and consistent in this craft. As with any other kind of nomenclature in programming, it's more of an art, and comes with lots of experience.

Advancing forwards, since components are instantiated in React as element (just like element also represent DOM elements), one might be tempted to think that setting event-handler props on component instances would have an effect. However, this is NOT the case.

Events do NOT get dispatched on component instances in React; they only get dispatched on elements representing DOM elements.

This means that if we have the following code,

function App() {
   return (
      <h1>A clickable heading</h1>
   );
}

const root = ReactDOM.createRoot(document.querySelector('#root'));
root.render(
   <App onClick={() => alert('Clicked.')}/>
);

nothing would happen when we click on the <h1>, simply because the onClick attribute on <App> has absolutely no effect, whatsoever.

But then why do we see such examples out there, providing event-handler props to components?

Well, those props are just named as if they are event-handler props, yet they aren't. These props are just like all other props passed to the components and are merely provided to be passed down to React elements representing DOM elements (which do receive events) returned by those components.

Shown below is an example:

function Button({onClick, children}) {
   return (
      <button onClick={onClick}>{children}</button>
   );
}

function App() {
   return (
      <Button onClick={() => alert('Button clicked.')}>A simple button</Button>
   );
}

Inside the App component, we render a <Button>, providing it an onClick prop. For the Button, there's nothing special about onClick; it's just that it takes the prop and passes it down to the <button> element that it renders.

Live Example

Simple?

The most important thing to notice in all these examples is the naming of the event-handler prop. It's called onClick, following the camel-case style; NOT onclick, in lower-case.

Why are event-handler props in React camel-cased?

Following the camel-case ideology of JavaScript itself, added to the fact that all event-handler props in React don't actually represent native DOM event handlers, React decided to follow the camel case convention for the nomenclature of event-handler props.

this in React's event handlers

From your early reads of JavaScript, in particular of events, you would've learnt that an event handler in JavaScript has its this configured to the node that has the event handler defined, unless this is explicitly changed to some other object by means of bind().

Here's a review of this concept:

<div>A div</div>
let divElement = document.querySelector('div');

divElement.onclick = function() {
   console.log(this === divElement); // true
}

The log clearly confirms that the this of the onclick handler is divElement, and that's simply because the onclick handler has been set up on divElement.

This, however, is NOT the case in React.

In React, the this of event hander functions is simply undefined.

When we click the <h1> element here,

function App() {
   return (
      <h1 onClick={function() { console.log(this); }}>A heading</h1>
   );
}

we get the following console log:

undefined

Now this does make some sense. First of all, note that this could only point either to the React element instance or to the underlying DOM element instance.

There would've been no point of going with the former, i.e. a React element instance. This is because there's nothing to do with a React element — it's just an element's object representation.

Talking about the latter, we could already access it via the target and/or currentTarget properties of the received event object. So this wouldn't serve any special purpose in this case as well.

It's only when we're dealing with class components that using this inside handlers could make sense. We first manually bind the handler to the class instance using bind() and then wait for the handler to fire, upon the dispatch of an event, at which point its this points to the class instance (not the React element).

The nativeEvent property

So we now know that event handler functions in React receive a synthetic event object, inheriting from React's SyntheticEvent interface, wrapping over the native DOM event.

But a fair question is: could we really somehow access the native DOM event, and if yes, then how?

Well, yes it's possible to do so. And that's done via the nativeEvent property of the SyntheticEvent event object passed on to the event handler function in React.

Let's consider an example.

In the following code, we see whether the fired event and its nativeEvent is based on the Event interface or not:

function App() {
   function onClick(e) {
      console.log(e instanceof Event);
      console.log(e.nativeEvent instanceof Event);
   }

   return (
      <h1 onClick={onClick}>A heading</h1>
   );
}
false true

Since the fired event e is a synthetic event, inheriting from React's SyntheticEvent interface (which inherits from Object), the first log is false as e is not an instance of Event.

But because the nativeEvent property does point to the actual DOM event, which inherits from Event, e.nativeEvent is an instance of Event, and hence the log true.

The truth is that in most cases, we may not need to use nativeEvent at all.

React itself does make sure that each synthetic event has a considerable amount of information available on it, based on the WHATWG DOM standard, that warrants not having to access nativeEvent.

But if we ever want to, now we at least know that we have a property to access it, although in almost all cases we won't need to.

Handler functions are closures

In JavaScript, we sparingly use event handlers defined within other functions. In other words, in JavaScript, we almost always define event handlers in the global context.

These handlers, likewise, either use the fired event object or certain global variables to perform their desired reaction to the event's occurrence.

In React, once again, this is usually NOT the case.

As you may already know, we usually have state data defined inside components in React apps. In order to be able to access this state data, it's paramount to define event handlers right inside the components so that they could close over the component's local context. (Remember closures?)

Consider the following counter program:

function Counter() {
   const [count, setCount] = useState(0);

   function onClick() {
      setCount(count + 1);
   }

   return (
      <div>
         <h1>Count: {count}</h1>
         <button onClick={onClick}>Increment</button>
      </div>
   );
}

function App() {
   return <Counter/>;
}

The Counter component defines some state data and an element with an onClick handler. This onClick handler needs access to count and is, likewise, defined inside the component.

Live Example

If we were to extract the handler out of the component, the program would no longer work:

// Handler extracted out of Counter.
function onClick() {
   setCount(count + 1);
}

function Counter() {
   const [count, setCount] = useState(0);

   return (
      <div>
         <h1>Count: {count}</h1>
         <button onClick={onClick}>Increment</button>
      </div>
   );
}

function App() {
   return <Counter/>;
}

The moment we click the button, an error is raised:

Uncaught ReferenceError: setCount is not defined ...

The reason why this happens is pretty simple. The onClick function is defined in the global context, yet it accesses the setCount() function (and the count state variable) which is defined inside Counter. So clearly, it won't be able to resolve setCount.

Live Example

Even if, somehow, an event handler doesn't access state or any other local data of a component, it's still advised to define it inside the component. This is considered good practice since it encapsulates all the logic related to a component inside that component.

So, instead of doing something like the following:

function onClick() {
   console.log('Heading clicked.');
}

function App() {
   return (
      <h1 onClick={onClick}>A clickable heading</h1>
   );
}

it's much better to do it this way:

function App() {
   function onClick() {
      console.log('Heading clicked.');
   }

   return (
      <h1 onClick={onClick}>A clickable heading</h1>
   );
}

Side effects in event handlers

Before we end this chapter, it's worth discussing about one of the core ideologies of React in relation to events That is, event handlers in React can perform side effects.

So do you know what are side effects?

What are side effects?

Well precisely, 'side effects' is a term used in regards to functions. At its simplest, a function is said to have a side effect when it mutates any non-local piece of data.

For example, if a function sets a timer (by calling setTimeout()), this is an instance of a side effect. Similarly, if a function sends an HTTP request to a server, this is another instance of a side effect. If it changes any piece of data stored on the global window object, we have, once again, a side effect.

Functions with side effects are referred to as impure functions. On the other hand, functions with side effects are referred to as pure functions.

Components in React, as we know by now, are pure functions and that's not because it's a syntactic requirement we ought to fulfill, but because it is another core principle of React, i.e. to keep components pure (functions), that we ideally should abide by.

Based on this principle, we are NOT ideologically allowed to perform side effects directly inside components.

For instance, if we want to set up a 2s timeout, displaying an alert when it completes, we can't do this directly inside a component. React proposes another way to do so and that we shall explore in the React Effects chapter.

For now, it's important to understand that one place where we can, and almost always do, perform side effects in components is inside event handlers.

So going with the same example as above, if we want to set up a 2s timeout upon the click of a button in React, we can very easily do so using the onClick handler function.

Whatever side effects we want to perform, we can do them inside event handlers in React. As simple as that.

Shown below are two practical examples.

The first one is simply what we stated above — the 2s timer setup. Here's the code for it:

function App() {
   function onClick() {
      setTimeout(() => {
         alert('The timer completed!');
      }, 2000);
   }

   return (
      <>
         <p>After pressing the button below, wait for 2s before noticing an alert.</p>
         <button onClick={onClick}>Start the timer</button>
      </>
   )
}

Live Example

Super basic.

The second example is a little bit more complex.

We fetch some public information from our GitHub account @codeguage-code upon the click of a button and display it inside a <div>.

Here's the code:

function App() {
   const [info, setInfo] = useState();

   async function onClick() {
      const data = await fetch('https://api.github.com/users/codeguage-code');
      setInfo(await data.json());
   }

   return (
      <>
         {info && (
            <div className="info">
               <p>Name: {info.name}</p>
               <p>Bio: {info.bio}</p>
            </div>
         )}
         <button onClick={onClick}>Fetch info</button>
      </>
   );
}

The fetching gets done via the fetch() JavaScript function. Once it completes, we update the info state datum to the received piece of data from GitHub's API, ultimately triggering a render to display the info inside the <div>.

Live Example

The side effect here is that we perform a network I/O operation — dispatching a request and then receiving its response.

Hopefully, these two examples of quite varying scopes should give you the know-how to be able to appreciate what exactly is meant when we say that event handlers can perform side effects in React.