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 onBlur events not firing during unmount #12363

Open
taj-codaio opened this issue Mar 13, 2018 · 16 comments
Open

React onBlur events not firing during unmount #12363

taj-codaio opened this issue Mar 13, 2018 · 16 comments

Comments

@taj-codaio
Copy link

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.

@aweary
Copy link
Contributor

aweary commented Mar 27, 2018

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.

@jayphelps
Copy link
Contributor

jayphelps commented Apr 16, 2018

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

@jpkempf
Copy link

jpkempf commented Apr 26, 2018

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?

@gaearon
Copy link
Collaborator

gaearon commented Aug 17, 2018

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

@jpkempf
Copy link

jpkempf commented Aug 22, 2018

@gaearon sure, no problem! I've created #13459. This is my first issue so I hope everything's fine. :)

@zibra
Copy link

zibra commented Oct 9, 2018

same issue on my end

@aldendaniels
Copy link

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.

@bcbane
Copy link

bcbane commented Sep 24, 2019

This is also affecting me

@lukas1994
Copy link

Any updates?

@mx2323
Copy link

mx2323 commented May 23, 2020

ditto

@maltenuhn
Copy link

Same here, we’d be happy to contribute towards a fix if there is appetite for one

@Rheeseyb
Copy link

Rheeseyb commented Jul 8, 2020

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:

  1. Attaches the onBlur event handler directly to the underlying DOM element via a useEffect, requiring attaching a ref to the input itself
  2. Uses a forwardRef combined with compose-react-refs to ensure we can still pass through a ref and also have access to a ref inside the wrapper
  3. Uses a ref to track the previous event handler for cleanup (we couldn't rely on the useEffect 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)} />;
});

@Aetet
Copy link

Aetet commented Jun 10, 2022

Any progress on this issue?

@bennettdams
Copy link

bennettdams commented Oct 25, 2022

Another workaround for conditional rendering

I wanted to submit a form via onBlur and encountered this bug. In my case, I was conditionally rendering either the data to display or a form to edit that data in the same place.
Something like:

  function Post({ isPostEditMode }: { isPostEditMode: boolean }): JSX.Element {
    return (
      <div>
        <p>Post</p>

        {isPostEditMode ? (
          // Form
          <form onBlur={...}>...</form>
        ) : (
          // Data
          <div>Data</div>
        )}
      </div>
    )
  }

Not sure if obvious for everyone, but there is another workaround:

Don't render the elements conditionally, but apply the display: none CSS property to either one of them. That way, the elements won't get re-/mounted and hence the onBlur callback is working as expected:

  // example uses Tailwind
  function Post({ isPostEditMode }: { isPostEditMode: boolean }): JSX.Element {
    return (
      <div>
        <p>Post</p>

        {/* Form */}
        <form
          className={isPostEditMode ? 'hidden' : 'block'}
          onBlur={...}
        >
          ...
        </form>

        {/* Data */}
        <div className={isPostEditMode ? 'block' : 'hidden'}>Data</div>
      </div>
    )
  }

bennettdams added a commit to bennettdams/summora that referenced this issue Oct 25, 2022
We now use conditional display instead of conditional rendering.
This is done so the form's `onBlur` is triggered, as there's a bug in React:

facebook/react#12363
@sanduluca
Copy link

sanduluca commented Jun 23, 2023

The problem still persists here is a snack with React Native

To reproduce:

  1. Use Android or iOS (on Web somehow its working)
  2. Focus the input
  3. Press the button
  4. The text will say that the input is focused (because onBlur was not called)

https://snack.expo.dev/@sanduluca/react-onblur-events-not-firing-during-unmount

@altyntsevlexus
Copy link

altyntsevlexus commented Mar 11, 2024

Here is a possible workaround. In my case I have a button that unmount component with input inside. So we can check an active element and trigger blur() before unmount.

const handleClose = () = > {
  const activeElement = document.activeElement;
  
  if (activeElement instanceof HTMLElement) {
    activeElement.blur();
  }
  
  close();
}

For proper usage we need to check if the active element is an HTMLElement, not just Element.

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