Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When is good practice to use stopPropagation()?

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?

like image 838
Kleo Avatar asked Feb 21 '19 20:02

Kleo


People also ask

Why do we use stopPropagation?

stopPropagation() The stopPropagation() method of the Event interface prevents further propagation of the current event in the capturing and bubbling phases. It does not, however, prevent any default behaviors from occurring; for instance, clicks on links are still processed.

What does e stopPropagation do?

Definition and Usage The event. stopPropagation() method stops the bubbling of an event to parent elements, preventing any parent event handlers from being executed. Tip: Use the event. isPropagationStopped() method to check whether this method was called for the event.

What is event stopPropagation () in angular?

stopPropagation() Event Method The stopPropagation() method prevents propagation of the same event from being called. Propagation means bubbling up to parent elements or capturing down to child elements.

In which situation we can use preventDefault?

The preventDefault() method cancels the event if it is cancelable, meaning that the default action that belongs to the event will not occur. For example, this can be useful when: Clicking on a "Submit" button, prevent it from submitting a form. Clicking on a link, prevent the link from following the URL.


1 Answers

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>
like image 94
customcommander Avatar answered Sep 27 '22 16:09

customcommander