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.
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>
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
.
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.