I have created 2 components in a parent child relation. The first component is using refs and in componentDidMount I added a click event listener on the DOM node, in the child component also I added a onclick listener but this time using the props on the DOM element.
But now when I click the child component the event listener of the parent component is getting invoked, and if I add the onClick on parent component using the props instead of refs everything works fine.
So, can anybody tell me why this behaviour is shown when we add event listener using the refs.
class SecondDiv extends React.Component {
divClicked = (event) => {
event.stopPropagation();
console.log('Second Div clicked');
}
render() {
return <div className="Div2" onClick={this.divClicked} >
This is second div
</div>
}
}
class MovieItem extends React.Component {
constructor(props) {
super(props);
this.node = React.createRef();
}
componentDidMount() {
this.node.current.addEventListener('click', this.divClicked);
}
componentWillUnmount() {
this.node.current.removeEventListener('click', this.divClicked);
}
divClicked = (event) => {
event.stopPropagation();
console.log('First div clicked');
}
render() {
// 1st approach
return <div ref={this.node} className="Div1" >
<p>This is Div 1</p>
<SecondDiv />
</div>
// 2nd approach
// return <div ref={this.node} className="Div1" onClick={this.divClicked} >
// <p>This is Div 1</p>
// <SecondDiv />
// </div>
}
}
ReactDOM.render(<MovieItem />, document.getElementById("app"));
<div id="app"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
It's because React uses event delegation to provide synthetic event handlers, you're using stopPropagation
on your DOM event handler. Since the handlers it uses are on the root element (id="app"
in your case), if you use a DOM handler to stop the event from propagating, React doesn't see it and doesn't trigger your event handlers.
Your elements are like this:
+−−−−−−−−−−−−−−−−−−−−−−+ | #app | | +−−−−−−−−−−−−−−−−−+ | | | .Div1 | | | | +−−−−−−−−−−−+ | | | | | .Div2 | | | | | +−−−−−−−−−−−+ | | | +−−−−−−−−−−−−−−−−−+ | +−−−−−−−−−−−−−−−−−−−−−−+
React's DOM handler is on #app
. Your DOM handler is on .Div1
. When you click .Div2
, it bubbles to .Div1
where your DOM handler sees it and stops it, so it never reaches #app
. Since it doesn't reach #appp
, React doesn't do its synthetic event for .Div2
.
In general, don't use DOM event handlers when you can use React event handlers instead. But if you need to use a DOM handler, and you want React's handlers to also run, don't prevent propagation.
Here's that example with stopPropagation
commented out in the DOM handler:
class SecondDiv extends React.Component {
divClicked = (event) => {
event.stopPropagation();
console.log('Second Div clicked');
}
render() {
return <div className="Div2" onClick={this.divClicked} >
This is second div
</div>
}
}
class MovieItem extends React.Component {
constructor(props) {
super(props);
this.node = React.createRef();
}
componentDidMount() {
this.node.current.addEventListener('click', this.divClicked);
}
componentWillUnmount() {
this.node.current.removeEventListener('click', this.divClicked);
}
divClicked = (event) => {
// event.stopPropagation(); // <============== Removed
console.log('First div clicked');
}
render() {
// 1st approach
return <div ref={this.node} className="Div1" >
<p>This is Div 1</p>
<SecondDiv />
</div>
// 2nd approach
// return <div ref={this.node} className="Div1" onClick={this.divClicked} >
// <p>This is Div 1</p>
// <SecondDiv />
// </div>
}
}
ReactDOM.render(<MovieItem />, document.getElementById("app"));
<div id="app"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
As you can see, it sees both events now, but as you noted in a comment, you see them in an odd order:
First div clicked Second Div clicked
That's, again, because React's click handler is on #app
, so it doesn't fire its synthetic event for .Div2
until the DOM event for .Div1
has already been fired.