I have a small React 16 menu panel that is running in a Rails 6 application. When the panel is open, I want to close it and stop propagation, if the user clicks outside the panel. (I don't want an accidental click outside the menu to take the user to another page.)
The general approach is quite well understood - here's one question that describes it. In brief:
- Using native JavaScript (i.e.
document.addEventListener('click', callback)
) inside of the React component, listen for the click event anywhere. - When that click event fires, check if the element (i.e. event.target) is within the div of the React menu.
- If the click was outside of the React menu, close the menu, and call
event.preventDefault()
andevent.stopPropagation()
to stop accidental navigation.
This all works fine in my simple test case. (Here's a fiddle, just to prove it works.)
However, it fails in my nested Rails application, if the user clicks on an anchor tag, because Rails's Unobtrusive JavaScript adds listeners to all anchor tags that contain the data-method attribute. The unobtrusive JS then hijacks the event, and injects an HTML form into the body. The target of the click event becomes an input of that new form. You can see the form being dynamically created when I click on a link in the panel here:
This means that the event.target
property is the input in this dynamically-injected form, and not the actual a
tag the user clicked on.
So my question is: How can I get the actual element the user clicked on, which triggered the creation of the form.
Stuff I've tried/thought of:
- Not using
data-method
. This works for the links in my menu. But then I cannot do POST events like logout, and any links created with Rails's helper methods. - Listening for
onmousedown
instead ofonclick
. In this case, the event's target is the correct element, but calling preventDefault() doesn't actually prevent the click. - Using forms inside my menu component instead of anchor tags. This way, I can do a POST for the logout action, without having to use the
data-method
attribute which introduces the problem described here. And it just works here, since if I click a link in the Rails app, I know it's outside the menu. But it's a hacky workaround, and it doesn't give me flexibility if I want to listen to some other clicks in the future.