React click event bubbling "sideways", not just "up"
Asked Answered
B

1

6

I have nested click event handlers within a component:

class ListItem extends React.Component {
    ...
    render() {
        return (
            <div onClick={this.save}>
              ...Content...
              <span onClick={this.onDeleteClick}> (Delete)</span>
            </div>
        );
    }
    ...
}

(Complete, minimal example at the bottom of this post.)

This component is used as a "list item" within a containing component. When I click on the (Delete) it fires the onDeleteClick as expected, which makes a callback to the parent which results in the component being removed from the parent component. The click event then starts bubbling up, as expected. However, it bubbles "up" to the next component in the parent list. After all, the original target was already removed by the delete handler.

If I add an e.stopPropagation() to the top of the onDeleteClick handler it all works fine, but I'm just trying to understand why the click event is being delivered to a completely different component, just to make sure there aren't any other smoking guns in there.

My current theory is that the pending event queue is somehow indexed into the virtual DOM in such a way that if you mutate the virtual DOM during an event handler, bubbling events could get delivered to the incorrect component in the virtual DOM. I would have expected the bubbling event to simply not fire, rather than firing on a completely different component.

Is this what's going on? Any other insight? Is this a flawed design, and if so, any recommendations on alternatives?


Here's a minimal example showing the problem: https://codepen.io/mgalgs/pen/dRJJyB

And here's the fixed version: https://codepen.io/mgalgs/pen/KqZBKp

The full diff for the "fix" is:

--- orig.jsx
+++ new.jsx
@@ -32,6 +32,7 @@
     }

     onDeleteClick(e) {
+        e.stopPropagation();
         this.props.onDeleteClick(e);
     }
 }
Bellanca answered 29/6, 2017 at 0:29 Comment(2)
Actually, this is really curious: codepen.io/anon/pen/rwpZNK?editors=1111 --- If you check the CodePen console it seems to bubbling up correctly to the correct parent, but if you check the developer console, it's bubbling to the wrong parent.Uthrop
I'm suspecting that because react relies on keys, it might actually be pushing it to the 'next' element, because it now occupies the original deleted elementHalden
C
3

+1 Thanks for posting an interesting question.

The behaviour seems inline with W3C's recommendations that:

The chain of EventTargets from the event target to the top of the tree is determined before the initial dispatch of the event. If modifications occur to the tree during event processing, event flow will proceed based on the initial state of the tree.

This may go some way to explaining what is happening here. The DOM tree seems to have been modified before the bubble propagates to the parent element by which time the event will target the element in the tree in that position (if any).

In this context, the element that triggered the event is removed from the tree. Rather than the event bubbling to the next element, it bubbles to the element that has now taken its position within the colleciton of elements where the initial element was, if any.

Chladek answered 29/6, 2017 at 2:6 Comment(2)
Knowledge increase! Thanks! I guess the key takeaway is: Don't mutate* the DOM in an event handler unless you also stop the event from propagating. (* = possibly only applies to deletion of any of the target nodes up the tree for the event)Bellanca
Actually, more generally, perhaps we can say: Don't do a setState in an event handler unless you can satisfy one of the following: (1) You absolutely know that it won't result in any of the targets for the currently processing event to be removed. (2) You stop the propagation of the event.Bellanca

© 2022 - 2024 — McMap. All rights reserved.