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

React's blur may not have relatedTarget in IE 9-11 where it is supported. #3751

Closed
dantman opened this issue Apr 26, 2015 · 16 comments
Closed

Comments

@dantman
Copy link
Contributor

dantman commented Apr 26, 2015

ReactBrowserEventEmitter has some handling for onBlur and onFocus.

See:

} else if (dependency === topLevelTypes.topFocus ||
dependency === topLevelTypes.topBlur) {
if (isEventSupported('focus', true)) {
ReactBrowserEventEmitter.ReactEventListener.trapCapturedEvent(
topLevelTypes.topFocus,
'focus',
mountAt
);
ReactBrowserEventEmitter.ReactEventListener.trapCapturedEvent(
topLevelTypes.topBlur,
'blur',
mountAt
);
} else if (isEventSupported('focusin')) {
// IE has `focusin` and `focusout` events which bubble.
// @see http://www.quirksmode.org/blog/archives/2008/04/delegating_the.html
ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent(
topLevelTypes.topFocus,
'focusin',
mountAt
);
ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent(
topLevelTypes.topBlur,
'focusout',
mountAt
);
}
// to make sure blur and focus event listeners are only attached once
isListening[topLevelTypes.topBlur] = true;
isListening[topLevelTypes.topFocus] = true;

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.

@jimfb
Copy link
Contributor

jimfb commented Apr 28, 2015

cc @syranide, our resident event expert these days.

@syranide
Copy link
Contributor

@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?

@dantman
Copy link
Contributor Author

dantman commented Apr 28, 2015

#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.

@busticated
Copy link

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

@peter-perot
Copy link

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.

@tvararu
Copy link

tvararu commented Jul 18, 2017

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.

@robatwilliams
Copy link

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.

@craigkovatch
Copy link

We just hit this too and were very surprised to not find it normalized by React.

@jquense
Copy link
Contributor

jquense commented Aug 24, 2018

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.

@craigkovatch
Copy link

@jquense IE (at least 11) sets document.activeElement before blur events fire. What's wrong with using that when it's available?

@catamphetamine
Copy link
Contributor

@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.

@dantman
Copy link
Contributor Author

dantman commented Aug 26, 2018

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.

@lukescott
Copy link

lukescott commented Feb 6, 2019

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.

@StJohn3D
Copy link

StJohn3D commented Jun 6, 2019

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 the onBlurCapture 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

@craigkovatch
Copy link

@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.

@gaearon
Copy link
Collaborator

gaearon commented Aug 10, 2020

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.

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