ReactBrowserEventEmitter has some handling for onBlur
and onFocus
.
React will first check if it can trap focus
with a capturing event using addEventListener
. If it can't and the browser supports focusin
then it will use focusin
.
However there is a problem with this pattern. Internet Explorer implements relatedTarget
on focusin
and focusout
but it does not implement it in focus
and blur
. As of IE 9, IE supports addEventListener and trapping capturing events.
This means that IE 8 and before React will have relatedTarget
on onFocus
and onBlur
handlers. But in IE 9-11 relatedTarget
will be null in React's onFocus
and onBlur
handlers even though IE would support it if focusin
and focusout
had been used.
cc @syranide, our resident event expert these days.
@jsfb Haha, I'm not _that_ familiar with the events actually ;)
Anyway, #2011 is kind of related. If the value that's meant to go in relatedTarget
is available elsewhere in the event then it would obviously be easy to normalize, but I doubt it is, which makes it unlikely that we can/want (fragile, etc, etc). Generally speaking though, you should never need to access relatedTarget
in React so I'm curious what you need it for?
The fix for this bug is probably the simple change of using focusin wherever it is supported instead of only using it when addEventListener isn't supported. ie: Flip the order we if ( ) {} else if ( ) {}
.
I make use of relatedTarget because I've gone the extra effort to make my navigation respond to the keyboard, based on WAI-ARIA's recommendations on how doing this with the keyboard should function. relatedTarget is relevant to a form of "close on blur" functionality. When navigating with the keyboard menu items are given focus one at a time and when focus leaves the menu (user has tabbed out, clicked somewhere else, or taken any other action that has moved focus out of the menu) the menu closes. Because any form of delegated blur event (which is required when you are trying to tell if focus has left a region rather than if a single form field is no longer focused) fires on every single blur event within a region, relatedTarget is necessary to tell the difference between the user moving from one menu item to another and focus leaving the menu. As a bonus, because I focus the menu container itself when a user has normally opened the menu by a click, I get a free way to automatically close the menu when the user clicks somewhere else. This type of menu closing is actually a lot less of a hack than other methods, like trapping clicks on the body, covering the page in a transparent overlay, etc.
yep, @dantman 's technique is the only sane way i've found to handle the whole "close when I click away / click outside of this component" UX
Any idea how to get this to work? Or any workaround? Please see http://stackoverflow.com/questions/38019140/react-and-blur-event/38019906#comment63483092_38019906.
Same issue when building an autocomplete and having to make heads/tails whether a fired onBlur
event should lead to closing the menu or not. onFocusOut
works fine in React but it leads to warnings being fired.
Related: #6410 "onFocusIn/onFocusOut events"
Contrary to @tvararu 's experience above, using onFocusOut
didn't call my handler (16.2.0). Neither did onFocusout
. The warning is "React uses onFocus and onBlur instead of onFocusIn and onFocusOut. All React events are normalized to bubble, so onFocusIn and onFocusOut are not needed/supported by React."
I ended up adding a focusout
event listener manually via a ref.
We just hit this too and were very surprised to not find it normalized by React.
its not possible to normalize unfortunately. The only viable approaches are timeout based and have caveats and failure cases.
This should probably be marked as wontfix
or similar, there really isn't anything React can practically do generally to avoid this. For individual use-cases or app specific workarounds the approach outlined here: https://github.com/jquense/react-component-managers/blob/master/src/focusManager.js generally works if you can deal with the timeout.
@jquense IE (at least 11) sets document.activeElement
before blur events fire. What's wrong with using that when it's available?
@craigkovatch
If you're interested in the topic, I pulled off exactly the same trick recently to make it work in IE.
https://github.com/catamphetamine/react-responsive-ui/blob/77b7ebc2e43b35595b06cda3ac697b75505e2174/source/utility/focus.js#L30
Basically, it uses setTimeout()
with document.activeElement
in IE and event.relatedTarget
otherwise.
its not possible to normalize unfortunately. The only viable approaches are timeout based and have caveats and failure cases.
This should probably be marked as wontfix or similar, there really isn't anything React can practically do generally to avoid this.
Not sure I should need to reiterate the topic of the issue. Normalizing would be impossible if this were an issue of a browser not supporting relatedTarget
and trying to normalize it in, like obsolete versions of Firefox. But that is not the issue, IE 9-11 does support relatedTarget
. The problem is that React binds the wrong event. React listens to events that don't have relatedTarget
instead of the proper events that do have relatedTarget
. This request never asked for React to attempt to polyfill something that wasn't available, it only asked for React to listen to the correct event (which it already had code targeting IE8 to handle that).
This discrepancy has only gotten worse over time. To my knowledge Firefox fixed its issue and implemented relatedTarget
, removing the issue of one browser not supporting it resulting in all browsers supporting it. IE11 however has stuck around, yet relatedTarget
doesn't work in React despite IE11 like all other browsers with notable userbases supporting relatedTarget
.
The back and forth on this is frustrating. I get wanting to normalize to onFocus
/ onBlur
, just like onChange
is normalized. But having relatedTarget not work in IE11 but work on other browsers is broken if the intent is normalization. This either needs to be fixed (prioritize onfocusin/onfocusout) or expose onFocusIn/onFocusOut as attributes.
Manually binding dom events or doing timeouts is not a great workaround, especially if the "fix" is changing the order of the if/else if clause. This has been open since 2015.
For my use-case specifically: I have a bunch of inputs within a component and I need to know if focus left the component. Setting a boolean from false => true => false between onBlur/onFocus doesn't work in my case because I need to do something onBlur, but only if newly focused element is not also in the component. relatedTarget is perfect for this - but IE 11 is still used, sadly.
So, I've had mixed results with this in IE11. In one of my react apps ( 16.8.4 ) - I can reliably get an element from event.relatedTarget
in theonBlurCapture
event. But then in another app it's always null, and I'm not sure why. So as a work-around I'm doing this...
const newTargetElement = event.relatedTarget || document.activeElement
@StJohn3D I would recommend flagging the document.activeElement
behind some browser detection, to avoid unexpected results in other browsers when e.g. focus moves out of the frame.
I think we can close this because React 17 switches to focusin
and focusout
events internally. You can try it with react@next
and react-dom@next
. If you still have problems please file a new issue with a fresh repro.
Most helpful comment
2011 is related, but this is separate. Since that's about what can be done when a browser has failed to implement relatedTarget at all. While this is about React not exposing relatedTarget when it is implemented in a browser.
The fix for this bug is probably the simple change of using focusin wherever it is supported instead of only using it when addEventListener isn't supported. ie: Flip the order we
if ( ) {} else if ( ) {}
.I make use of relatedTarget because I've gone the extra effort to make my navigation respond to the keyboard, based on WAI-ARIA's recommendations on how doing this with the keyboard should function. relatedTarget is relevant to a form of "close on blur" functionality. When navigating with the keyboard menu items are given focus one at a time and when focus leaves the menu (user has tabbed out, clicked somewhere else, or taken any other action that has moved focus out of the menu) the menu closes. Because any form of delegated blur event (which is required when you are trying to tell if focus has left a region rather than if a single form field is no longer focused) fires on every single blur event within a region, relatedTarget is necessary to tell the difference between the user moving from one menu item to another and focus leaving the menu. As a bonus, because I focus the menu container itself when a user has normally opened the menu by a click, I get a free way to automatically close the menu when the user clicks somewhere else. This type of menu closing is actually a lot less of a hack than other methods, like trapping clicks on the body, covering the page in a transparent overlay, etc.