When is good practice to use stopPropagation()?
Asked Answered
C

1

8

There have been some attempts to answer this question: here, here and here. However none of the answers are giving a solid response. I'm not referring to the event phases capture, bubble and target and how stopPropagation() affects the overall event. I'm looking for a case where adding stopPropagation() to a DOM node will benefit the overall code?

Cavalry answered 21/2, 2019 at 20:9 Comment(11)
OK, I don't get what the question is. You benefit from stopPropagation() when you don't want the event to propagate further. Yes, I just told you "you need it when you need it" but I don't see a way to explain this that's not circular if you already know what propagation is.Clanton
If you don't get the question and don't see a way to explain it, please refrain from answering. "You need it when you need it" doesn't provide any insight in coding.Cavalry
"benefit the overall code" — it benefits the code when propagation to parent DOM nodes would result in undesirable behavior. It's not just something you toss in for good measure; it has a specific purpose and in a given situation you either do want to stop propagation or you don't.Thithia
OK, what exact issue you have with understanding why you don't want event propagation to happen? You've linked to three different other questions and the information given there is the same. You don't need it when you don't need it. If you have a situation like this where you fire multiple click handlers but you might need only some. You can stop the propagation on the button, for example. If you're not in this situation, it's not needed.Clanton
@Pointy, can you give me an example of undesirable behavior? Can you give me an example in a given situation when you want to stop propagation?Cavalry
There is the JSBin I linked to. That's an example. Multiple event listeners for the same event on multiple elements in parent-child hierarchy. A click triggers all.Clanton
@Clanton you are providing a code that simply lay's out event bubbling. How is this even remotely close to what I'm asking? Let me clarify with an example. You would use preventDefault() when submitting a form in a front-end app and you would prevent the a page refresh.Cavalry
"you are providing a code that simply lay's out event bubbling" yes, that's correct. That's what stopPropagation stops. That's why I don't understand what you don't understand about stopPropagation. "How is this even remotely close to what I'm asking?" Let's be even more clear - let's say you want only the button click handler to engage, not the others. But you still want the other click handers that do different stuff. You also can't change the HTML. Your only option is stopPropagation. You have to use it, so you use it.Clanton
"Let's be even more clear - let's say you want only the button click handler to engage"...why? Why do I want only the button click to engage? I don't ask what stopPropagation do, you are attempting over and over to tell me what ti does. Please read my question that I state clearly when to use it.Cavalry
@Kleo I have stated it clearly. You want only the button click to engage, not the rest. You do still want to handle clicks to the background. Why - I can't tell you because when you get to that situation, you'd know. I can make up a reason, one that sounds plausible and you are very likely to encounter but you since I expect you to just reject it, so I won't bother. You keep saying I'm not giving you a reason but you've not given me a question, either. It's like asking "when do I need to use a for loop" and then "but why would I use for loop" when I give you an example.Clanton
"Why - I can't tell you because when you get to that situation, you'd know." and " I can make up a reason, one that sounds plausible" really doesn't go hand in hand with "I have stated it clearly", anyway please submit your comments as an answer and not as a comment if you believe you were clear enough.Cavalry
B
23

This really shouldn't be an answer, but there is only so much you can write in a single comment


I don't think you're doing your question justice by having the words "good practice" in its title. This sort of implies that in most cases, stopPropagation is bad practice. This is similar to saying that eval is evil. It completely brushes off any legitimate use cases of it with misplaced dogmatism.

I never found myself in a situation where using stopPropagation didn't feel like a workaround to avoid fixing the real issue.

In an ideal world, applications are built out of smaller components that do very little on their own but are highly reusable and combinable. For this to work, the recipe is simple yet very difficult to execute: each component must know nothing about the outside world.

Therefore if a component needs to use stopPropagation(), it can only be because it knows that something further up the chain will break or that it will put your application into an undesirable state.

In this case you should be asking yourself whether that is not a symptom of a design issue. Perhaps you need a component that orchestrates and manages the events of its children?

You should also consider the fact that preventing the propagation of events can cause other components to misbehave. The classic example is a drop-down that closes when you click outside of it. If that click is stopped, your drop-down may never close.

Think of events as sources of data. You don't want to lose data. Au contraire! Let it go, let it free ;)

While I don't see using stopPropagation as bad or evil practice, I just don't think it is ever needed.


Example: how to avoid using stopPropagation

In this example we're building a very simple game: if you click on a red area you lose, on a green area you win. The game is over once a click is made.

const onClick = (selector, handler) => {
  document.querySelector(selector).addEventListener('click', handler);
};

onClick('#game', () => console.log('game over'));
onClick('#red', () => console.log('you lost'));
onClick('#green', () => console.log('you won'));
#red, #green { width: 50px; height: 50px; display: inline-block; }
#red { background-color: orangered; }
#green { background-color: yellowgreen; }
<div id="game">
  <div id="red"></div>
  <div id="green"></div>
</div>

Now let's imagine that there are different levels in which red and green blocks are arranged randomly. In level #42, the red block contains the green one.

const onClick = (selector, handler) => {
  document.querySelector(selector).addEventListener('click', handler);
};

onClick('#game', () => console.log('game over'));
onClick('#red', () => console.log('you lost'));
onClick('#green', () => console.log('you won'));
#red, #green { max-width: 100px; padding: 10px; }
#red { background-color: orangered; }
#green { background-color: yellowgreen; }
<div id="game">
  <div id="red">
    <div id="green"></div>
  </div>
</div>

As you can see when you click on the green area, you both win and lose at the same time! And if you were to put a stopPropagation() call in the green handler, there will be no way to win this game since the click won't bubble up to the game handler to signal the end of the game!

Solution 1: identify the origin of the click

const filter = handler => ev =>
  ev.target === ev.currentTarget ? handler(ev) : null;

const onClick = (selector, handler) => {
  document.querySelector(selector).addEventListener('click', handler);
};

onClick('#game', () => console.log('game over'));
onClick('#red', filter(() => console.log('you lost')));
onClick('#green', () => console.log('you won'));
#red, #green { max-width: 100px; padding: 10px; }
#red { background-color: orangered; }
#green { background-color: yellowgreen; }
<div id="game">
  <div id="red">
    <div id="green"></div>
  </div>
</div>

The key function is filter. It will make sure that handler will only execute if the click actually originated from the node itself and not from one of its children.

The currentTarget read-only property of the Event interface identifies the current target for the event, as the event traverses the DOM. It always refers to the element to which the event handler has been attached, as opposed to Event.target, which identifies the element on which the event occurred.

https://developer.mozilla.org/en-US/docs/Web/API/Event/currentTarget

Solution 2: use event delegation

You don't actually need three events handlers. Just set up one on the #game node.

const onClick = (selector, handler) => {
  document.querySelector(selector).addEventListener('click', handler);
};

onClick('#game', (ev) => {
  if (ev.target.id === 'red') {
    console.log('you lost');
  } else if (ev.target.id === 'green') {
    console.log('you won');
  }
  console.log('game over');
});
#red, #green { max-width: 100px; padding: 10px; }
#red { background-color: orangered; }
#green { background-color: yellowgreen; }
<div id="game">
  <div id="red">
    <div id="green"></div>
  </div>
</div>
Beni answered 21/2, 2019 at 22:30 Comment(6)
Stop propagation is needed when you have JavaScript running on the same event of nested elements. Imagine having a click event on a parent element AND a child. If you clicked the child, and don’t want it to also count as a click for the parent, then you need to stop propagation in the child click handler. This isn’t common, but at times it’s necessary.Stereogram
It should be relatively easy to figure out the origin of the click though. With that the parent can decide whether it can ignore that click or not. I’m happy to demonstrate this if you think that’d be useful.Beni
I guess I’m trying to understand the purpose of the parent realizing that the click was intended for the child, vs the child being like “I got this” and stopping propagation. It just feels like a specificity thing. Either way, the two need to be aware of each other to know that only one of them should handle the click. Can you explain the purpose of avoiding stopPropagation()? Currently I’m stuck thinking “The feature exists for a reason. Handling it in the parent is overcomplicated and probably slower.”. I’d love to understand what you’re saying.Stereogram
@Stereogram I have edited my answer to include some examples to show how to avoid using stopPropagation(). I hope that you (and @Kleo) find them useful.Beni
I am confused on the logic for example 1. Can you break it down step by step? I think it mainly has to do with the syntax of all the statements. Example 2 makes perfect sense... for the most part. In example 2, I don't understand why in the bubble up why the event wouldn't be fired anyways, meaning: 1. #game is clicked (let's say on #green), so, if I understand correctly, it should go #green, #red, #game, which would mean, 2. #green would be fired, then #red, then #game, which would mean, 3. it would still display win, lost, then no result. Maybe I don't understand events well enough.Olethea
Had to remind myself that "onclick" wouldn't fire that function multiple times, because those elements don't have the event handler attached to them, in response to my question in example 2. That being said a full explanation (even explaining some of the syntax) might be helpful. Other than that, spectacular answer. Left an upvote for you :DOlethea

© 2022 - 2024 — McMap. All rights reserved.