React: onFocusIn/onFocusOut events

Created on 5 Apr 2016  Â·  60Comments  Â·  Source: facebook/react

Like mouse enter/leave, these are almost always what you want, not the onFocus and onBlur events we currently expose. I run into this semi-frequently when actually doing product work. We should add them.

DOM Partner Feature Request

Most helpful comment

Hey @gaearon @spicyj apparently there is a confusion here, let me explain.
mouseover and mouseout bubble up. mouseenter and mouseleave don't bubble up. Corresponding React event handlers work the same as native events - awesome.

Native focus and native blur events don't bubble. Corresponding React event handlers onFocus and onBlur do bubble - dun dun dun.

Native focusin and native focusout events do bubble. React events for these should be implemented and bubble too.

Here is a jsfiddle I made to display this. Checked in Chrome only, as since MDN documentation states that this is intended behavior then I believe it should be the same in other browsers.

If I am right about this (please correct me if I am wrong), then the following should be done:

  1. Warnings about onFocusIn/onFocusOut should be removed (obviously).
  2. Currect React onFocus should be renamed to onFocusIn; onBlur - to onFocusOut as they currently work the same as native onFocusIn and onFocusOut work - they bubble.
  3. onFocus and onBlur event listeners should be implemented in React - which will not bubble.

All 60 comments

FWIW, I agree with you. People know about focusin/focusout, and so we should just make them work, despite the fact that they would map to the exact same event handler we use for focus/blur.

However, last time this was discussed, we opted to not do this (https://github.com/facebook/react/pull/6226) and decided to warn instead (https://github.com/facebook/react/pull/6296/files).

I'd be fine with reversing this decision if you can get the momentum within the team.

It looks like it was decided against because of an incorrect claim that they're indistinguishable from onFocus/onBlur. onFocusIn/onFocusOut is different in that when moving focus between two children of an element, neither event fires on the container.

Doing it properly means adapting the logic in EnterLeaveEventPlugin and isn't as simple as adding it to SimpleEventPlugin.

Ah, that makes sense. Thanks for explaining!

Adding “good first bug” here. Note @spicyj’s comments: they need to behave like onMouseEnter and onMouseLeave. You’ll want to look at EnterLeaveEventPlugin.

I'd like to work on it if nobody minds. Enough with the tests, time to add real source code!

Hey @gaearon @spicyj apparently there is a confusion here, let me explain.
mouseover and mouseout bubble up. mouseenter and mouseleave don't bubble up. Corresponding React event handlers work the same as native events - awesome.

Native focus and native blur events don't bubble. Corresponding React event handlers onFocus and onBlur do bubble - dun dun dun.

Native focusin and native focusout events do bubble. React events for these should be implemented and bubble too.

Here is a jsfiddle I made to display this. Checked in Chrome only, as since MDN documentation states that this is intended behavior then I believe it should be the same in other browsers.

If I am right about this (please correct me if I am wrong), then the following should be done:

  1. Warnings about onFocusIn/onFocusOut should be removed (obviously).
  2. Currect React onFocus should be renamed to onFocusIn; onBlur - to onFocusOut as they currently work the same as native onFocusIn and onFocusOut work - they bubble.
  3. onFocus and onBlur event listeners should be implemented in React - which will not bubble.

Hmm, that sounds plausible. Thanks for the correction. Kinda annoying that we'll end up renaming these for people but maybe it's for the best.

Hey, unfortunately, I cannot invest time in this right now and I must pause. I have added onFocusIn/Out by mimicing onFocus/onBlur but I am having issues with changing onFocus/onBlur so they don't bubble.
I don't want to slow down the awesomeness of React so if anyone wants to invest into this issue - please do. If nobody does - I will get back to it when I have more time. Good luck!

but I am having issues with changing onFocus/onBlur so they don't bubble.

Do we want them to not bubble? We had previously said that all events bubble. I'm fine with whatever, so I'll leave that decision to @spicyj since he has the most situational awareness here.

Since this is tagged as a good first bug I thought I'd take a stab at this. Let me know if you're still working on it @cbrwizard !

I did a little more investigating and it looks like focusin and focusout events are not supported by Firefox. Is this still something you want to implement if all browsers don't support it? See here: https://developer.mozilla.org/en-US/docs/Web/Events/focusin and here: https://bugzilla.mozilla.org/show_bug.cgi?id=687787

@mrscobbler It should be possible to polyfill the event on any browsers that don't natively support it. We do this with several events that are not supported on particular browsers.

Ok! Good to know.

Any status on this?

So I've spent a few hours figuring out the events code (I've never looked at it before) and I've made some progress. Possibly @cbrwizard could walk me through some of what he's done? I might get a better understanding of what needs to happen. @trigun539 is it something you want to tackle or do you just need/want it finished?

@mrscobbler @spicyj Was it ever decided that this is something that should still be implemented? Seems like the discussion of whether or not this should be added based on the fact that all events currently bubble kind of ended without a consensus. I'd be willing to help out with this if we decide to go further with it.

I am working on an app that needs to be 508 compliant, and have a flyout menu where the last inner submenu li element should fire a focusOut event (for the parent li) when you go past it. However, it doesn't fire it. I have added the onBlur event and it doesn't work as expected. I can take a look at this.

Navigation is all done through keyboard.

@trigun539 Can you post a code example by any chance? I just created a basic example with a ul and two li's that each have an inner anchor and after adding onFocus and onBlur events to the li's, the events fire correctly and bubble up from the inner anchors.

@anthonybarsotti the set up is more around the following:

`


  • Home

  • Parent Item with Submenu

    • Submenu item 1
    • Submenu item 2(last li)


  • Another test link

`

If I tab when I am on the "last li" I should go to "Another test" li, but it should fire an onBlur event on the "parent item" li. I'll post a sample after I get it set up.

@mrscobbler Can we please work on it together? I wish to contribute.

@rishirajsurti Yes! I'll send you an email. @anthonybarsotti @spicyj has it been decided whether or not we should move forward with this?

if anyone here needs some insight on the event code, feel free to ping me. I've some experience with it, (PR's in the past) and would also like to see this.

ALSO the current mouseEnter/Leave logic is crazy I really don't think we should emulate it again for new features if possible. I've already spoken to @jimfb a bit about that though. For insight as well there is this outstanding PR which simplifies the mouse logic, it might be helpful stratgey for this case as well: https://github.com/facebook/react/pull/5762

Wait, are we trying to add focusIn/Out as analogs to mouseEnter/Leave? Personally I rarely want the behavior of native focus/blur events. I do however almost always want for focusEnter/Leave like events...

@jquense Yeah I think that's the idea, the bigger question is that since the current onFocus and onBlur events bubble should those events just be renamed and have new events implemented for onFocus and onBlur that don't bubble?

Thats where my confusion comes from. mouseEnter and mouseLeave _do not_ bubble; that's much of the point of them, but they also _do not_ behave like native focus/blur, which only fire when a the element that attached the handle gains focus _not_ when it or any of its children gain focus.

so I'm not sure why we'd need to invert the behavior, unless the goal is to not actually add something like focusEnter/Leave

I know this might be unrelated / wrong thread for this, but when I use onFocus or onBlur events on an input element in my React app, if i set the value to an empty string in the event handler, I get TypeError·TypeError: property "value" is non-configurable and can't be delete

Looking at the stack trace, it's because it is trying to delete the value and detach the onPropertyChange handler: attachEvent("onpropertychange",d):A.addEventListener("propertychange",d,!1)}function f(){A&&(delete A.value,A.detachEvent?A.detachEvent("onpropertychange",d):A.removeEventListener("propertychange",d,!

any idea what might be causing this?

@iam-peekay Please file a separate issue, ideally with a repro case that exhibits the problem.

It seems like this issue has stalled, so let me weigh in with a summary. There are two separate features that a focus/blur event handler can provide:

  1. bubbling: If the focus/blur event fires on a descendant node, this handler gets called.
  2. encapsulation: If focus passes between two descendant nodes, this handler does not get called.

If I understand correctly, here's how I think the current handlers are defined:

| Implementation | focus handler | blur handler | bubbles | encapsulated |
| :--- |:--- | :--- | :---: | :---: |
| React | onFocus | onBlur | Yes | No |
| Native | onFocus | onBlur | No | Yes[1] |
| Native | onFocusIn | onFocusOut | Yes | No[1] |

There are two changes being discussed in this issue:

  1. Bringing the react event spec in-line with the DOM. It seems to me like simply adding a check for event.target === event.currentTarget would be enough to turn a bubbling event into a non-bubbling one, but I'm not sure whether there needs to be more work on error handling or something, and whether that approach would work with React Native. It seems like the impetus behind this change is about consistency with the DOM.

  2. Creating a variant of the focus event that is encapsulated. This can be done in the DOM world with event.currentTarget.contains(event.relatedTarget), but that requires querying the DOM. Not sure if this is possible with RN. This change is about adding a new behavior to React.


[1] [The spec](https://www.w3.org/TR/DOM-Level-3-Events/#events-focus-types) does not explicitly describe this behavior, but it is implied by the requirements. It can also be confirmed in this fiddle

I'll jump back here since I dealt with this once again :P Thanks @felipeochoa for the table.

In my experience I almost never want either of the native focus events (tho react should offer them), instead I want focus events that do not bubble and are encapsulated (as termed above)

I'd suspect tho at this point that may be unlikely as it would require a new polyfill

O i'd add that you can't actually adapt the mouseEnterLeave logic for this, since IE11 doesn't have a relatedElement set on the focus/blur events :(

Hi folks 🙂 any progress on this?

Similar to what @jquense mentioned, in IE10/11, blurEvent.relatedTarget is null. (Microsoft is tracking - and not fixing this so far! - here and here.) focusOutEvent.relatedTarget is populated as expected.

But React strips out onFocusOut props, thereby leaving no way to access relatedTarget in IE10/11.

@awreccan For the time being, you might be able to use the following workaround for relatedTarget:

setTimeout(() => {
    const relatedTarget = document.activeElement;
    // ...
}, 0);

As I see, this issue is still open, can anyone update us on the progress, please?

I need onFocusOut so I can run things if the user focuses out of the whole element, opposed to onBlur which will fire if any child gets the focus.

Meanwhile, is there any workarounds?

Here is an example of how I would use onFocusIn to toggle a user experience:

https://codepen.io/jonneal/pen/JpdmBm/

^ In this example, I’ve used the focusin and focusout events to trigger a classname change on a parent element. The parent contains two visible controls. We can focus on either control to trigger focusin and reveal additional siblings. With these additional elements revealed, we can now focus on the original controls or these newly revealed elements without changing the "focused" state of the parent element. We can also focus off all of these elements to trigger focusout and hide the additional elements; making things appear as they did before.

Using defaultValue attribute worked for me in case of onblur.

The encapsulation behaviour is really strange and unpredictable. Is it expected to be solved?

I ran into this issue when working with window.getSelection(); there are workarounds, such as onmouseout, onmouseup, and document.onselectstart. But it would be nice to have an input onfocusout to grab the selection at that event time.

can anyone update us on the progress

If you don't see any updates, it means there have not been any updates.

I agree it would be great to solve it. If you can write up a proposal for how it should work, and what the migration steps would be, that would help move the progress forward. :-)

As of React 16.5.2 focus event bubbles field is always set to false meanwhile it is still bubbling.

I think this is an issue and _(a separate report must be probably filled, but anyway)_ must be addressed either

  • by making it consistent with normal behavior that was mentioned above
  • or
  • by just fixing bubbles into true in order to eliminate confusion.

Here is an example of bubbling event: https://codesandbox.io/s/6lron03zyw

...
<span onFocus={this.onFocus}>
      <input type="text" value={placeholder} />
</span>

I need onFocusOut so I can run things if the user focuses out of the whole element, opposed to onBlur which will fire if any child gets the focus.

@M-Dahab I don't think you need onFocusOut for this. You can use an onBlur handler like this:

handleBlur = (e) => {
if (!e.currentTarget.contains(e.relatedTarget)) {
  // focus is leaving the container so do something interesting here
}

@craigkovatch this is precisely why we need onFocusOut. Some browsers do not set event.relatedTarget for the blur event. Eg: Firefox (see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/relatedTarget)

FWIW, focusin & focusout have been supported by Firefox since version 52 and the current one is 67. The latest Firefox ESR still supported is at v60. We can assume focusin & focusout to be available in all browsers React should care about IMO, relying on the capture phase of focus & blur is not necessary anymore.

@jonathantneal's example curiously does not work as expected for me in Firefox 68, despite Firefox allegedly having support for the events. Can anyone else reproduce?

Link: https://codepen.io/jonneal/pen/JpdmBm/

@eneroth works fine for me on FF 68.0.1, with the caveat that FF does not focus buttons by default when you click them, so I had to tab to it to get the focusin handler to activate for that.

@craigkovatch Alright, thanks. Here's the difference that I'm seeing: http://eneroth.com/gamX3cfz8yBAdiBx4qjxboGm/Firefox%20vs%20Chrome.mov
I.e., the buttons appear when the text field gains focus, then disappear immediately when the text field loses focus. With Chrome, the buttons stay visible.

@eneroth ok I see that too, but I think that is the same thing: buttons not focusing on click in Firefox -- so they wouldn't emit the bubbling focusin event necessary to keep the div displayed. i.e. the text field blurs but the clicked button doesn't focus. If you click into the text field you can tab into the revealed buttons.

More bad news -- it's OS dependent:
https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#Clicking_and_focus
https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#Clicking_and_focus

Easy to fix (as proposed here)

document.addEventListener(
  "click",
  event =>  event.target.focus();    
);

I want to use onFocusOut to hide an INPUT that I use to edit the content of a TD. I need to use an INPUT because the need to use the type attribute to select the correct keyboard in an Android Device.

https://caniuse.com/#search=focusout

It seems that all modern navigators implements focusout

Coming back to this thread, I'm not sure what is being asked here. If I understand correctly:

  • People are asking to support onFocusIn and onFocusOut events.
  • However, React already bubbles onFocus and onBlur events, which is analogous to the browser behavior for onFocusIn and onFocusOut.

I'm not sure that everybody who commented this actually have the same request in mind. If this issue is still relevant to you, can you please restate what you are asking? Taking into account that "add onFocusIn" doesn't mean much because React onFocus already behaves like onFocusIn. Thanks!

I'm not sure that everybody who commented this actually have the same request in mind. If this issue is still relevant to you, can you please restate what you are asking?

I would have to think pretty hard to remember the case that motivated me to subscribe to this and to link it from #13525. I believe I had a case where I thought it would be nice if onFocus and onBlur did _not_ bubble, but behaved like the browser events. Then, having onFocusIn and onFocusOut would be useful because they do bubble.

@gaearon my request is that React internally listen to focusin/focusout rather than focus/blur, because there is information missing in the latter (e.g. relatedTarget, see https://github.com/facebook/react/issues/6410#issuecomment-477858045). But keep the same bubbling behavior as now.

Ahh, yes, @craigkovatch might actually be onto what was causing me trouble.

I believe I had a case where I thought it would be nice if onFocus and onBlur did not bubble, but behaved like the browser events.

This makes sense. The workaround is to check e.target === e.currentTarget for those cases. Maybe someday we can change this.

my request is that React internally listen to focusin/focusout rather than focus/blur, because there is information missing in the latter

All right, this is good to know. We've actually made that exact change on master so it should go out in 17.

Maybe someday we can change this.

Yeah, that's why I brought it up in https://github.com/facebook/react/issues/13525#issuecomment-465728057 in the context of other breaking changes to align with how DOM events work. From that issue's description around changes to onChange and onInput (emphasis mine):

It has been confusing that React uses a different event name for what's known as input event in the DOM. While we generally avoid making big changes like this without significant benefit, in this case we also want to change the behavior to remove some complexity that's only necessary for edge cases like mutating controlled inputs. So it makes sense to do these two changes together, and use that as an opportunity to make onInput and onChange work exactly how the DOM events do for uncontrolled components.

In summary, I don't have a specific need and there are workarounds, but it could be nice to align with the DOM if that means fewer surprises. Of course, there's an argument to be made that the DOM events are surprising or unintuitive, but I'm not here to argue this either way. I'm all set! Glad to know relatedTarget is coming.

If we are asking for things what I want is focusenter/focusleave like we have for mouse and pointer events. Given that they aren't stanardized tho it's probably not a good fit for react dom

Yeah most of our recent work has been to align closer with the DOM so this is not a good fit at this stage. But there's been parallel work on better accessibility primitives so I think maybe it's in that scope.

In React 17, onFocus and onBlur internally listen to focusin and focusout.

It seems like this addresses the core of this issue (https://github.com/facebook/react/issues/6410#issuecomment-663731372). It's fair that maybe it would be nice to eventually rename them to align with the browsers (and possibly, add non-bubbling versions). Please feel free to raise new issues for these topics.

To try React 17, install react@next and react-dom@next.

Thanks to everyone for the very informative discussion!

I made a small example demonstrating the current behavior based on https://github.com/facebook/react/issues/6410#issuecomment-292895495:

https://codesandbox.io/s/strange-albattani-7tqr7?file=/src/App.js

export default function App() {
  return (
    <div
      tabIndex={1}
      onFocus={(e) => {
        console.log("focusin (self or child)");
        if (e.currentTarget === e.target) {
          console.log("focus (self)");
        }
        if (!e.currentTarget.contains(e.relatedTarget)) {
          console.log("focusenter");
        }
      }}
      onBlur={(e) => {
        console.log("focusout (self or child)");
        if (e.currentTarget === e.target) {
          console.log("blur (self)");
        }
        if (!e.currentTarget.contains(e.relatedTarget)) {
          console.log("focusleave");
        }
      }}
    >
      <input />
      <input />
    </div>
  );
}

Hopefully this helps as a quick reference when you need to decide on which check to use.

I think there's definitely room for improvement here but if we're talking about API changes, a detailed RFC would be the best way to move this forward: http://github.com/reactjs/rfcs

I'll lock the discussion so people don't lose this canonical answer when coming from Google, but feel free to file new issues if something is unclear or not working.

Was this page helpful?
0 / 5 - 0 ratings