5 Must-Know Facts on React Event Handling

5 Must-Know Facts on React Event Handling

·

7 min read

A key part of building any non-trivial front-end application involves managing user interactions.

React applications are no exception. When a user performs an action, it triggers an event that can be handled. What sets React apart is the way it manages these events. Understanding these differences will make it easier to build interactive React applications.

Keep these 5 tips in mind when handling events in React:

TL;DR

  1. Don't Execute Functions; Pass References.

  2. Wrap Functions That Take Arguments.

  3. Use CamelCase for Event Handler Props.

  4. Events Bubble Upwards but are Captured Downwards.

  5. Beware of Default Browser Event Handlers.

1. Don't Execute Functions; Pass References.

When you pass a function to an event handler, make sure you pass a reference to the function. A common problem I've seen is that a developer will execute the function instead of passing a reference:

const breakApp = () => { /* Stuff */ };

function MyComponent() {
    return <button onClick={breakApp()}>Push to Break</button>;
}

The breakApp function is executed immediately during MyComponent's render cycle instead of when the button is clicked. Unfortunately for your users, this means they don't get the satisfaction of getting to break the app themselves. They're stuck with a broken app from the start.

So how do you fix the app, so that your users can break the app?

Just don't execute the function.

Instead, pass a function reference to the event handler. This will give React a chance to remember the function and execute it sometime in the future:

const breakApp = () => { /* Stuff */ };

function MyComponent() {
    return <button onClick={breakApp}>Push to Break</button>;
}

Passing a function reference to the event handler is easy enough to do, but there's a small problem. What if you want to pass arguments to your function? Keeping with the theme above, let's say that breakApp shouldn't work 100% of the time.

Instead, we'll pass a random number to breakApp, and based on that number, the function will succeed or fail. Once again, we need to make sure to pass a function reference to the event handler, so this won't work:

const breakApp = () => { /* Stuff */ };

function MyComponent() {
    const rng = randomNumberGenerator();
    return <button onClick={breakApp(rng)}>Push to Break</button>;
}

Luckily, this is easy to fix.

All you have to do is...

2. Wrap Functions That Take Arguments.

You can't pass arguments to event handler functions.

At least not directly.

So now what?

Remember, you just need to pass a function reference to your event handlers for things to work correctly. So let's do that. Wrap your function that takes arguments with another function that just executes the first one.

Yes. Yes.

It's functions all the way down:

const breakApp = () => { /* Stuff */ };

function MyComponent() {
    const rng = randomNumberGenerator();
    const wrappedFunction = () => breakApp(rng);
    return <button onClick={wrappedFunction}>Push to Break</button>;
}

Instead of executing breakApp immediately, it's wrapped by another function called wrappedFunction. Since wrappedFunction doesn't take any arguments, we can safely pass it to the onClick event handler.

3. Use CamelCase for Event Handler Props.

Most JSX attributes use camel case, and event handlers are no exception. This is different than standard HTML which uses lowercase for event handler names. In other words, you want to make sure to write onClick with a capital C instead of onclick:

// Wrong! `onclick`
function BadComponent() {
    return <button onclick={breakApp}>Push to Break</button>;
}

// Right! `onClick`
function GoodComponent() {
    return <button onClick={breakApp}>Push to Break</button>;
}

This is an easy mistake to make when you first start writing React code. Luckily React will warn you when you make a mistake like this, so it should be easy to recover from.

4. Events Bubble Upwards but are Captured Downwards.

Great!

So now you know how to attach an event handler to an element. But what if you wanted to track events for a group of elements? While you could attach an event handler to each element, there's another way.

Events bubble up the component hierarchy.

This means when you click on an element, not only will React execute the element's onClick handler, but React will also execute every parent element's onClick handler.

Let's take a look at the example below.

Try clicking on each of the numbered blocks and also on the background.

Notice how there's only one event handler in the code above that's attached to the div element that wraps the button elements. Even though the buttons don't have onClick handlers, an event bubbles up to the parent element when you click one.

This lets us handle all the events in one place.

This behavior is consistent for all React events, except for the onScroll event.

So what if you don't want an event to bubble up to a parent element? Don't worry, React has you covered. All you need to do to opt out of this behavior is to stop the event from propagating:

This time, button one has its own event handler. When you click this button, the first thing we do is call event.stopPropagation(). This tells React to make sure it doesn't execute the onClick handler for any parent elements.

Now when you click button one, we only log the text in the button's handler.

But what if you still want the parent to respond to the button click?

In rare cases, you still might want to handle the button click from a parent handler.

Let's say you were using a 3rd party button component that calls event.stopPropagation() internally when clicked. In this case, you can't remove that code because you don't own it. In other words, there's no way for you to handle the click from a parent element...

...or is there?

You can capture the event.

React captures events down the element hierarchy.

You can attach special event handlers to your React components that let you handle an event even if a child element calls event.stopPropagation().

All you need to do is add the word Capture to the end of your event handlers. For click events, the handler is called onClickCapture. To understand why this works, we need to take a closer look at the order in which these event handlers are executed:

When you trigger an event (e.g. by clicking a button), it moves in the following way:

  1. It moves down the element hierarchy, calling onClickCapture for every element

  2. It calls the onClick event handler for the element you clicked

  3. It moves up the element hierarchy, calling onClick for every element

This works because your code runs during step 1, but event.stopPropagation() prevents step 3.

Try clicking on the button in the example below. It should log the order of the event handlers.

5. Beware of Default Browser Event Handlers.

Some events have a default browser implementation.

If you're not careful, this can lead to some unexpected behavior. For example, most browsers refresh the entire page for the form element's onSubmit event.

For Single Page Applications, it's common to prevent this behavior and handle the submission yourself.

Luckily, it's easy to do this with React:

function MyForm() {
    function onSubmit(event) {
        event.preventDefault();
        // Do other form stuff after...
    }

    return (
        <form onSubmit={onSubmit}>
            {/* Form stuff goes here */}
        </form>
    );
}

The call to event.preventDefault() prevents the browser's default implementation.

In the example below, the second form prevents the browser's default implementation while the first form doesn't. Try clicking both submit buttons to see the difference.

When the first button is clicked, the entire page refreshes.

To make this more obvious, enter a value into the input field and then submit the form. When the page refreshes from the first button click, we lose the value stored in the input element.

preventDefault vs stopPropagation

One thing to keep in mind is that while it may seem like there's some overlap between preventDefault and stopPropagation, both methods have different purposes:

  • preventDefault: This has to do with preventing the browser's event handlers

  • stopPropagation: This has to do with preventing the developer's event handlers on a parent element