Do you want to request a feature or report a bug?
bug
What is the current behavior?
If a DOM element rendered by a React component has focus, and the React component unmounts, the React onBlur event does not fire on parent DOM elements.
If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem. Your bug will get fixed much faster if we can run your code and it doesn't have dependencies other than React. Paste the link to your JSFiddle (https://jsfiddle.net/Luktwrdm/) or CodeSandbox (https://codesandbox.io/s/new) example below:
https://codesandbox.io/s/134wrzy6q7
What is the expected behavior?
I would expect that, just like the browser fires a focusout event when removing a DOM node, React would fire an onBlur events up to parent nodes when the focused node is removed / unmounted.
Which versions of React, and which browser / OS are affected by this issue? Did this work in previous versions of React?
React: 16.2
Mac OS X: 10.13.2
Browser: Chrome 67.0.3366.0, Chrome 64.0.3282.186
No idea if this worked in earlier versions of React.
I suspect this is a result of how React handles events that occur during reconciliation. Currently, React disables the synthetic event system before committing. That means events that occur as a result of node removal aren't actually handled.
We've talked about having an event queue that gets processed after the commit phase, but right now there's no current plan to do that.
Sounds like rather than onBlur bubbling we'd want focusout support via onFocusOut since browsers don't bubble native blur events and doing so in React could cause bugs by breaking those expectations.focusout is not supported yet though for various reasons: https://github.com/facebook/react/issues/6410
Today I learned that React actually _already bubbles blur events_, unlike browsers, which makes me sad 馃槩Now I understand why this issue was created.
I have noticed similar behaviour with the onChange event. I'm not sure if it merits a separate issue, so I'm posting a response here first. I'm happy to move my comment to a new issue, if need be.
Here's a reduced test case (open the console and click around a bit): https://codesandbox.io/s/84wz9wlvxl
In this scenario, once you check one of the boxes, it's place in the virtual DOM changes. As a consequence of that, the change event gets lost while the click event bubbles up to the document. If I have understood @aweary correctly, this is currently the intended behaviour from the point of view of React, yes?
@jpkempf A separate issue is best for something that doesn't look like what's being described by this issue's title. Because otherwise it's unsearchable. Thanks.
@gaearon sure, no problem! I've created https://github.com/facebook/react/issues/13459. This is my first issue so I hope everything's fine. :)
same issue on my end
Also hitting this - and another simple repro case: https://codepen.io/anon/pen/NEQbjM?editors=1112
Here you can see that the native focusout event does fire, but there's no corresponding synthetic onBlur event.
This is also affecting me
Any updates?
ditto
Same here, we鈥檇 be happy to contribute towards a fix if there is appetite for one
For those still hitting this, we came up with a workaround here: https://codesandbox.io/s/determined-euler-t7ijw?file=/src/App.js
In a nut shell, what we did was use a wrapper which:
onBlur event handler directly to the underlying DOM element via a useEffect, requiring attaching a ref to the input itselfforwardRef combined with compose-react-refs to ensure we can still pass through a ref and also have access to a ref inside the wrapperuseEffect clean up to do this because I believe that happens during the reconciliation, meaning the event handler is removed before being called in this use case - I have verified this)There is one major caveat here though - because it requires attaching an event handler to the underlying element, it means that handler will be dealing with the raw event rather than React's SyntheticEvent, which may be a deal breaker for you.
To save you clicking the link, here's the code for the wrapper (which could equally have been written as a HOC):
import React from "react";
import composeRefs from "@seznam/compose-react-refs";
const WrappedInput = React.forwardRef((props, outerRef) => {
const { onBlur, ...inputProps } = props;
const innerRef = React.useRef(null);
const lastOnBlur = React.useRef(null);
React.useEffect(() => {
if (innerRef != null && innerRef.current != null && onBlur != null) {
if (lastOnBlur.current != null) {
innerRef.current.removeEventListener("blur", lastOnBlur.current);
}
innerRef.current.addEventListener("blur", onBlur);
lastOnBlur.current = onBlur;
}
}, [innerRef, onBlur]);
return <input {...inputProps} ref={composeRefs(outerRef, innerRef)} />;
});