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.
cc @syranide our resident event export these days
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.
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).
@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.
Actually I want to show/hide things when focused, not when I'm hovering. Think about a contextual dropdown menu for example.
@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.
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.
@leoasis event.stopPropagation()
I think?
@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.
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.
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
@zpao @leoasis OK, so event.nativeEvent.relatedTarget
is apparently always null
in FireFox (and IE11) for FocusEvent
s. 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.
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)
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
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.
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?
@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...).
@nbergseng I'm pretty sure the correct solution is to use focusin
and focusout
unless you have a very specific use-case.
@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.
@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.
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.
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.
@emecell won't that stop clicks being generated for elements inside that div?
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.
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.
@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.
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 Due to #3751 if you use explicitOriginalTarget
your code still won't work in IE9+.
If you're interested, here is the module I use to workaround this.
"use strict";
var React = require('react');
function relatedTarget(e, fn) {
// jshint browser: true
var {relatedTarget} = e;
setImmediate(() => {
var to = relatedTarget || document.activeElement;
fn(to);
});
}
relatedTarget.contains = function(root, to) {
var $ = require('jquery');
root = React.findDOMNode(root);
to = React.findDOMNode(to);
return root === to || $.contains(root, to);
};
module.exports = relatedTarget;
Basically I pollyfill setImmediate and use either relatedTarget from the event or the activeElement.
setImmediate is required because document.activeElement
may be wrong during the event itself.
The .contains
bit is optional, you don't need to use it. But I find it makes the most common use case (testing if focus has left a container) easier.
Thanks I'll keep that in mind when I try on IE
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!
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.
@dantman event.persist()
takes care of that
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?).
relatedTarget works with IE11 and focusout - not blur.
you can prob get what you want simply by doing a setTimeout({ let relatedTarget=document.activeElement; ... }. 0);
I am adding the event listener manually and using:
const relatedTarget =
event.relatedTarget || event.rangeParent || document.activeElement
~@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
This issue appears to be resolved in 15.6 with Firefox 55.0.3. https://jsfiddle.net/60ten3bw/
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
~@dantman THANK YOU! Your solution (getting the value of
document.activeElement
in asetImmediate
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.
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.
Most helpful comment
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 reasonablerelatedTarget
using the followingmaybe that will help someone.