Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

relatedTarget in blur event in Firefox returns null always #2011

Closed
leoasis opened this issue Aug 7, 2014 · 42 comments
Closed

relatedTarget in blur event in Firefox returns null always #2011

leoasis opened this issue Aug 7, 2014 · 42 comments

Comments

@leoasis
Copy link

leoasis commented Aug 7, 2014

Basically that, the relatedTarget is returning null all the time, when in Chrome it is returning the correct value if clicking on an actual element (will return null if clicking outside the document, which is ok).

This is something that Firefox is doing natively, so there should be some "patch" in that case to make it cross browser as a syntethic event.

Here is an example: http://jsfiddle.net/leoasis/kb3gN/4476/ Click the first input and then the second, and you'll see in the console: a) the relatedTarget if in Chrome, or b) null if in Firefox.

@zpao
Copy link
Member

zpao commented Aug 11, 2014

cc @syranide our resident event export these days

@syranide
Copy link
Contributor

Haha, I believe I've seen some code in there that's meant to fix this, I'll have a look tomorrow and see what's going on. @leoasis I'm curious what you need this for.

@leoasis
Copy link
Author

leoasis commented Aug 11, 2014

Basically I'm trying to show some contextual elements whenever I'm focused in a component. So I have an onBlur handler at the root of my component and use the relatedTarget (which in a blur event is the element where i'm going TO) and check wether that element is inside the root one or not to figure out if I'm actually leaving the component or not.

I worked it around right now by checking the same but with clicks, attaching the handler in the document object and doing the same check. It works, but only for clicks (pressing tab won't work for example).

@syranide
Copy link
Contributor

@leoasis That sounds like something you could/should solve with onMouseEnter and onMouseLeave. There's generally no reason to use the reported nodes in an event for React. That being said it should still be fixed.

@leoasis
Copy link
Author

leoasis commented Aug 12, 2014

Actually I want to show/hide things when focused, not when I'm hovering. Think about a contextual dropdown menu for example.

@syranide
Copy link
Contributor

@leoasis So onFocus and onBlur then? Or whichever event fits your use-case. It's your choice obviously, but I'm quite certain that your solution has a React-only solution.

@leoasis
Copy link
Author

leoasis commented Aug 12, 2014

That's actually what I used, the thing is that onBlur is bubbled to the root element in the component whenever anything inside is blurred. So I still want to check if the relatedTarget (the next element being focused) is still inside the component.

@syranide
Copy link
Contributor

@leoasis event.stopPropagation() I think?

@leoasis
Copy link
Author

leoasis commented Aug 12, 2014

@syranide I think that won't work in this case, since I don't want to handle the blur events in the children, I am handling the event in the top element inside the component, so I want to have the events propagated to that one.

@syranide
Copy link
Contributor

Yep, your choice, but AFAIK, the React-way would be to handle it in the children and propagate a callback through props instead. Anyway, I'm going to have a look at fixing this either way.

@leoasis
Copy link
Author

leoasis commented Aug 12, 2014

Thanks for taking a look at it!

Anyway, I think I'm still not clear in what I need to do, and I don't think the React way is to traverse the entire children adding blur and focus handles to just see if i'm blurring out of the component or not. To be clear, this is a simplified version of what I'm trying to do, in a fiddle:

http://jsfiddle.net/leoasis/VkebS/569/

Notice that I want to be able to select the , and also I want it to disappear when I click or tab outside, for example in the other input or anywhere else

@syranide
Copy link
Contributor

@zpao @leoasis OK, so event.nativeEvent.relatedTarget is apparently always null in FireFox (and IE11) for FocusEvents. The FocusEvent does not provide any information on the element being focused. So there doesn't seem to any easy fix for this that isn't relatedTarget = null.

I imagine it could be solved by simply keeping track of the last focused element, but I suspect that it's more fragile in practice than you would expect, so it would probably not be a great fix. It also doesn't really fit with how React is meant to be used...

So that's another interesting topic, how should/would you accomplish this with React? There's inherently nothing we can put in events that would make sense, I think. You could probably track focused element in the parent and if the parent discovers it doesn't have a focused element during componentWillUpdate (can't call setState there...) it would be the signal to close, batching should prevent the time between blur to focus from being a problem I think.

@syranide
Copy link
Contributor

Follow-up to the previous, I haven't tested it yet, but I imagine onFocusIn and onFocusOut is the correct solution to all problems? (both React and non-React)

@leoasis
Copy link
Author

leoasis commented Aug 14, 2014

If I'm not wrong, focusin and focusout events are not supported in Firefox yet: https://developer.mozilla.org/en-US/docs/Web/Events/focusin

@syranide
Copy link
Contributor

Hmm, it says listening to focus with capture should have a similar effect (onFocusCapture in React I think?), perhaps there's something there we can use to shim it... although the persistent threat of event.stopPropagation being called outside React's control is always there.

@nbergseng
Copy link

Ran into this recently too... hacked around it using nativeEvent.explicitOriginalTarget (FF only) if e.relatedTarget is null, though it feels pretty fragile. Would it make sense to normalize relatedTarget across browsers?

@ezequiel
Copy link

ezequiel commented Sep 5, 2014

@nbergseng I'm interested in knowing how explicitOriginalTarget was used in your solution.

We ran into this problem today as well, and hacked around it using document.activeElement alongside a timer (sigh...).

@syranide
Copy link
Contributor

syranide commented Sep 7, 2014

@nbergseng I'm pretty sure the correct solution is to use focusin and focusout unless you have a very specific use-case.

@ezequiel
Copy link

ezequiel commented Sep 8, 2014

@syranide Yes, it is the correct solution, but as @leoasis has stated, those events aren't supported in Firefox. Anyway, we ended up taking the focus with capture approach. It seems to be working well so far.

@syranide
Copy link
Contributor

syranide commented Sep 8, 2014

@ezequiel They kind of are, focus and blur bubbles on FF. Regardless it can be normalized by React, I don't remember if it is or isn't right now, but if it isn't we should normalize it.

@neonstalwart
Copy link

i don't use react but i came across this issue while searching for what to do about the missing relatedTarget in IE11. i'm not familiar with how react works with events but using raw DOM events i managed to get a reasonable relatedTarget using the following

function blurHandler(e) {
    var relatedTarget = e.relatedTarget ||
            e.explicitOriginalTarget ||
            document.activeElement; // IE11

    if (!relatedTarget || !e.currentTarget.contains(relatedTarget) && !element.contains(relatedTarget)) {
        // blur is moving to an element outside of the tree under currentTarget
    }
}

maybe that will help someone.

@emecell
Copy link

emecell commented Mar 3, 2015

One way we got around this issue was to stop the mouse down event being fired for the component that is listening to the blur event, e.g.:

handleBlur: function() { 
   if (!event.relatedTarget) {
     this.handleHide();
   }
 },

handleMouseDown: function(event) { event.preventDefault(); },

render: function() {
   <div onBlur={this.handleBlur} onMouseDown={this.handleMouseDown} tabIndex="-1">...</div>
}

This seems to work fine in all browsers though it'd be nice to get relatedTarget for IE/FF.

@neonstalwart
Copy link

@emecell won't that stop clicks being generated for elements inside that div?

@noinkling
Copy link

Thanks @neonstalwart, it will prove useful for me.

My use case is a form consisting of two fields, if I click or tab between them I don't want anything special to happen, but the moment both of those fields become unfocused (onBlur) I want to replace the form with another element (aka set some state) and do some other stuff.

@dantman
Copy link
Contributor

dantman commented Apr 26, 2015

Some of the facts and guesses in this bug are incorrect, so this issue probably isn't going to get fixed when everyone is looking at the wrong thing to fix. (Primarily the suggestion that onfocusout is a fix).

Firstly people here are correct that Firefox's lacks e.relatedTarget. This is a bug I wish would have been fixed years ago. See bug 962251.

onfocusout and onfocusin are not actually prerequisites of e.relatedTarget. The spec defines relatedTarget on blur and focus events. However while Chrome implements relatedTarget on blur and focus; IE does not and instead only implements relatedTarget on focusout and focusin events.

Next, the blur event does not bubble. blur will only fire when the exact element it is registered on loses focus. It will do so when lost to any other node even if that node is a descendant of itself (like for a focused tabIndex="0" div with focus lost to a button inside it). It will not fire if a descendant of it loses focus. It doesn't matter if that descendant loses focus to another descendant or to something outside the element blur is registered on. blur will not fire in either case.

jQuery mimics focusout by registering a capturing blur. Then focusout is used when you register a delegated blur event.

You can see this in this test. When the button loses focus blur is not fired on the container around it. While blur is fired on the button, focusout is fired on the container (including in Firefox where jQuery fakes it), and the delegated blur is fired.
http://jsbin.com/sunulohoke/2/

Which brings me to React.

React delegates all its events. This means that when you use onBlur React will register a capturing blur for the document if addEventListener is supported and otherwise will use focusout (which causes a separate bug I reported in #3751).

As a result onBlur in React will work alright on elements like <button>, <a>, <input>, etc... where there are no focusable children and you only care about when they lose focus and not where the focus goes. But it is worthless for use on containers. It does not behave as a blur handler on the container would (a blur event would not fire when a child loses focus) and instead fires every time any element within the container loses focus. And relatedTarget which is required to tell if that focus has left the container is only available in Chrome and IE 8 and before not Firefox and IE 9-11.

@dantman
Copy link
Contributor

dantman commented Apr 28, 2015

@neonstalwart Be aware that you probably don't want to use e.explicitOriginalTarget. Testing it myself, while it has the equivalent of e.relatedTarget when focus is lost due to a click. It has the wrong target when tab is used to change focus.

@Keats
Copy link

Keats commented Jul 21, 2015

Just spent ~2h on that before realising what was happening, that's a shame Firefox doesn't have it.
My usecase is showing a toolbar on mouseup on a div and keep the toolbar showing after clicks on it so explicitOriginalTarget should be enough most of the time I guess but still.

@Keats
Copy link

Keats commented Jul 21, 2015

Thanks I'll keep that in mind when I try on IE

@Keats
Copy link

Keats commented Jul 22, 2015

Looking at your comment without being half asleep like yesterday, it's much better than explicitOriginalTarget!
I already have my own contains (well isDescendant in my case).

Cheers!

@dantman
Copy link
Contributor

dantman commented Jul 22, 2015

Just don't forget that React's synthetic events purge themselves when the event is over. So any event property you want, will need to be copied to a local variable to use it in the callback.

@quantizor
Copy link
Contributor

@dantman event.persist() takes care of that

@fvgs
Copy link

fvgs commented Aug 22, 2015

It's a shame this bug hasn't been resolved yet. I recently implemented behavior for a search bar using event.relatedTarget only to discover later on that event.relatedTarget does not work with Firefox or Safari. In my experience, it works correctly on Chrome, but always returns null on Firefox and Safari.

My use-case was telling an event not to cause a component to be unmounted, so that component's onClick handler would have a chance to run. I circumvented this by using a timeout inside of the first event-handler so the other component would not be unmounted until its onClick handler had a chance to run. This solution works with negligible side-effects. However, it's far from elegant.

It would be great to have this be compatible with Firefox and Chrome. Bonus points if it ends up working on IE (maybe it already does?).

@drakir
Copy link

drakir commented Sep 4, 2015

relatedTarget works with IE11 and focusout - not blur.

@kofifus
Copy link

kofifus commented Sep 12, 2016

you can prob get what you want simply by doing a setTimeout({ let relatedTarget=document.activeElement; ... }. 0);

@aaronshaf
Copy link

I am adding the event listener manually and using:

const relatedTarget =
      event.relatedTarget || event.rangeParent || document.activeElement

@joshjg
Copy link

joshjg commented Aug 13, 2017

@dantman THANK YOU! Your solution (getting the value of document.activeElement in a setImmediate callback) was the only one discussed here that worked cross-browser (including phantomjs) for me.

Edit: Still don't have a solution for Safari. document.activeElement doesn't get set to a button

@aweary
Copy link
Contributor

aweary commented Sep 19, 2017

This issue appears to be resolved in 15.6 with Firefox 55.0.3. https://jsfiddle.net/60ten3bw/

@aweary aweary closed this as completed Sep 19, 2017
@Mathspy
Copy link

Mathspy commented Sep 19, 2017

Actually for documentation purposes in case someone stumbles back here this has been fixed in Firefox since Firefox 48 and I don't think it really depends on React version

I am not sure whether it was fixed on Safari or not yet

@dvaltasaar
Copy link

@dantman THANK YOU! Your solution (getting the value of document.activeElement in a setImmediate callback) was the only one discussed here that worked cross-browser (including phantomjs) for me.

Edit: Still don't have a solution for Safari. document.activeElement doesn't get set to a button

For Safari I use a simple solution: don't use a button element or if you need button you must insert into button tag an element like 'div' with 'tabindex="0"' and fire events on it.

@gaearon
Copy link
Collaborator

gaearon commented Aug 10, 2020

We're changing React to use focusin and focusout under the hood in 17 for onFocus and onBlur. Please feel free to try react@next and react-dom@next and file a new issue if there are any concerns.

@juanlanus
Copy link

Notice that there is another difference that was not mentioned in #6410: the blur event fires when the element has lost focus, while focusout happens just before the focus loss.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests