Skip to content

Instantly share code, notes, and snippets.

@pstoica
Last active September 9, 2024 05:14
Show Gist options
  • Save pstoica/4323d3e6e37e8a23dd59 to your computer and use it in GitHub Desktop.
Save pstoica/4323d3e6e37e8a23dd59 to your computer and use it in GitHub Desktop.
onBlur for entire react element
function OnBlurComponent({ onBlur }) {
const handleBlur = (e) => {
const currentTarget = e.currentTarget;
// Check the newly focused element in the next tick of the event loop
setTimeout(() => {
// Check if the new activeElement is a child of the original container
if (!currentTarget.contains(document.activeElement)) {
// You can invoke a callback or add custom logic here
onBlur();
}
}, 0);
};
return (
<div tabIndex="1" onBlur={handleBlur}>
Hello <input type="text" value="world" />
</div>
);
}
@sunnymui
Copy link

sunnymui commented May 28, 2020

Super useful! Just spent a day going through tons of pages on blur and focus to figure out an annoying bug that was preventing my onclick handlers in a child component from firing. I was using it for a dropdown component based on the details/summary html element to remove the open attribute on blur so only one dropdown menu would show at a time, but the blur event goes first, adding the open attribute, and triggering a rerender before the child component's onclicks could fire.

This also helps me out because one of the dropdowns I needed to keep it open when they clicked within it because there were some input interactions in the dropdown menu. Also had to add tabindex=0 to my dropdown element so the focus info would pass correctly.

The other thing was it wouldn't work for me until I set the timeout to a longer time, around 50-100ms. Not sure exactly why, but I'm guessing it's because React doesn't trigger renders immediately after changes, instead batching the changes then updating all at once. I think that extra time makes it wait long enough for that to happen before triggering my blur handler.

Here's what my the details element in my render function looked like:

 <details
      className={`DetailSummaryDropdown`}
      ref={dropDownRef}
      tabIndex='0'
      onBlur={(e) => {
          const currentTarget = e.currentTarget;
          // blur happens before click, preventing any click events in children from firing due to rerender from state change
          // so wait a tick for child component events to fire before changing open state and causing rerender
          window.setTimeout(() => {
            if (!currentTarget.contains(document.activeElement)) {
              dropDownRef.current.removeAttribute("open");
            }
          }, 100);
      }}
>

Anyway, just sharing my frustrations in case anyone else finds it helpful

@StanielPetrov
Copy link

OMG, you are a genius! Thank you so much for sharing this!

@HermanNygaard
Copy link

@pstoica
Thanks for this! Just a heads up, I think you forgot to change the callback onBlur={onBlur} to onBlur={handleBlur} in the newest revision:

return (
    <div tabIndex="1" onBlur={handleBlur}>
      Hello <input type="text" value="world" />
    </div>
  );

@pstoica
Copy link
Author

pstoica commented Mar 12, 2021

@pstoica
Thanks for this! Just a heads up, I think you forgot to change the callback onBlur={onBlur} to onBlur={handleBlur} in the newest revision:

return (
    <div tabIndex="1" onBlur={handleBlur}>
      Hello <input type="text" value="world" />
    </div>
  );

glad it helped. thanks for catching that, updated!

@AshlandWest
Copy link

You're my hero!

@gattonero1052
Copy link

That helps a lot!

@diegohaz
Copy link

You can also use event.relatedTarget to get the next active element on blur if you don't care about IE 11.

@YogeshR6
Copy link

YogeshR6 commented Sep 9, 2024

@diegohaz I was look for this for so long, thanks a lot. Was working with emoji pickers and for some reason document.activeElement does not work when I click on the picker itself and closes the pop over even though it is a part of the div, but using event.relatedTarget works perfectly fine now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment