0

I have made a Sidepanel component in React and have attached a click listener to close the panel when user clicks anywhere outside the Sidepanel component, like so:

function Sidepanel({ isOpen, children }) {
  const [isPanelOpen, setPanelOpen] = useState(isOpen);
  const hidePanel = () => setPanelOpen(false);
 
  ... 

  useEffect(() => {
    document.addEventListener('click', hidePanel);
    return () => {
      document.removeEventListener('click', hidePanel);
    };
  });

  return (
    <aside
      className={`side-panel ${isPanelOpen ? 'side-panel--open' : ''}`} 
      onClick={stopEventPropagation}
    >
      <div className="side-panel__body">{children}</div>
    </aside>
  );
}

function stopEventPropagation (e) {
  e.stopPropagation(); // <-- DOESN'T SEEM TO WORK
};

export default Sidepanel;

But, this code doesn't work as expected since the panel starts to close on a click even inside the <aside> element itself. It seemed like e.stopPropagation() did nothing so I updated the stopEventPropagation code to:

function stopEventPropagation (e) {
  e.nativeEvent && e.nativeEvent.stopPropagation();
};

...which also didn't work. But, when I did this:

function stopEventPropagation (e) {
  e.nativeEvent && e.nativeEvent.stopImmediatePropagation(); // <- IT WORKED!
};

...it worked. Weird!

I read some docs about stopImmediatePropagation I found on Google and realised that although it makes the code work, it just doesn't make any sense here. Am I missing something?

UtkarshPramodGupta
  • 7,486
  • 7
  • 30
  • 54
  • https://stackoverflow.com/questions/24415631/reactjs-syntheticevent-stoppropagation-only-works-with-react-events This will probably answer your question – Hassaan Tauqir Jun 21 '20 at 07:43

3 Answers3

0

Replace document.addEventListener by window.addEventListener so that event.stopPropagation() can stop event propagate to window.

I hope this will help you.

Shahnawaz Hossan
  • 2,695
  • 2
  • 13
  • 24
  • Can you please add more detail about why this should work? – UtkarshPramodGupta Jun 21 '20 at 07:37
  • You can find the details if you check [this question](https://stackoverflow.com/questions/24415631/reactjs-syntheticevent-stoppropagation-only-works-with-react-events). You can check [this issue](https://github.com/facebook/react/issues/4335) as well. – Shahnawaz Hossan Jun 21 '20 at 07:40
0

When talking about event.stopPropagation() or event.stopImmediatePropagation(), we are talking about the term Event bubbling (or Bubbling for short). So make sure you have knowledge about it, otherwise, please take a look at this article

In the above article, you may find this useful one:

If an element has multiple event handlers on a single event, then even if one of them stops the bubbling, the other ones still execute.

1. event.stopPropagation() stops the move upwards, but on the current element all other handlers will run.

2. To stop the bubbling and prevent handlers on the current element from running, there’s a method event.stopImmediatePropagation(). After it no other handlers execute.

Back to your problem, it belongs to the later case. You have two onClick handlers attached to document, so using event.stopPropagation() is not the case here, but event.stopImmediatePropagation().

---Update 1:

React (currently) uses a listener on the document for (almost) all events, so by the time your component receives the event it has already bubbled all the way up to the document.

That means, the later onClick handler which is attached to the aside element will bubble up to document.

Cam Song
  • 492
  • 2
  • 11
0

If you added the event listener normally (by window.addEventListener()) then e.stopPropagation() should work. But if you added it via React (by onClick) then it won't work, because React uses its own event processing which itself listens on document. So by the time the onClick handler on aside runs, the event may have been bubbled up and reached document, where your hidePanel() listener fired.

e.nativeEvent.stopImmediatePropagation() might cancel all listeners before that happens and solve your problem, but blindly bypassing event handlers (possibly added by some UI library) might cause side effects. So a better approach that is not affected by all the confusing event propagation, might be handling the logic within the document click listener.

    const hidePanel = (e) => {
        // Only hide panel if clicked outside it
        if (!e.target.classList.contains('side-panel')) {
            setPanelOpen(false);
        }
    }
Son Nguyen
  • 1,412
  • 4
  • 10