Event Bubbling

September 20th, 2015
Updated: January 27th, 2018

Have you ever been unsure of where to put your onChange or other event handler in React? Does it go in the parent, or the child? Can you put a handler in both places? Should you pass event handlers with props?

If you've ever asked yourself any of those questions, this article is for you.

The DOM

Let's start by talking about how event bubbling works without React, in the regular DOM.

Consider this simple page written in vanilla, non-React, Javascript code. Go ahead, click the button.

Notice that when you click the button, 'Button Click' is printed BEFORE 'Parent Click'. This is an example of an event beginning at its target (the button) and then bubbling up through its parents. Here's the code - notice that we simply add a click handler to the button and its parent.

<html>
<body>
  <div id="parent">
    <button id="button">
      Click Me
    </button>
  </div>
  <div id="log" style="white-space: pre-wrap;">Log:</div>
  <script>
    var button = document.getElementById('button');
    var parent = document.getElementById('parent');
    var log = document.getElementById('log');

    button.addEventListener('click', function(event) {
      log.appendChild(document.createTextNode("Button Click . "));
    });

    parent.addEventListener('click', function(event) {
      log.appendChild(document.createTextNode("Parent Click . "));
    });
  </script>
</body>
</html>

React

Okay, so you never write code without a sweet framework. So how does this work in React?

Consider the following React component, NameTitle, which has one subcomponent, NameInput:

The NameInput component is the inner component: It wraps an HTML <input> element, listens for onChange, and sets the background color to pink if the input is empty.

import React from "react";

class NameInput extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      empty: true
    };
  }

  handleChange = event => {
    this.setState({
      empty: event.target.value.length === 0
    });
  };

  inputStyle() {
    if (!this.state.empty) {
      return {};
    }
    return {
      backgroundColor: "pink"
    };
  }

  render() {
    return (
      <input
        style={this.inputStyle()}
        onChange={this.handleChange}
        placeholder="Type Your Name"
      />
    );
  }
}
export default NameInput;

NameTitle: Wraps the NameInput component, listens for onChange, and prints your name in the header.

import React from "react";

import NameInput from "./NameInput";

class NameTitle extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      name: ""
    };
  }

  handleChange = event => {
    this.setState({
      name: event.target.value
    });
  };

  render() {
    return (
      <div onChange={this.handleChange}>
        <h2>Hello {this.state.name}</h2>
        <NameInput />
      </div>
    );
  }
}
export default NameTitle;

The parent component, NameTitle captures the onChange from an <input> in its subcomponent.

This can be a great strategy for adding more event-handling behaviour to existing components, without passing a ton of event handlers in props.

Conclusion

Event handling in React works very similarly to the regular DOM. onChange, onFocus, onBlur and other event handlers can be added directly to the target element or to any of its parents - even in other components.