-
-
Save pstoica/4323d3e6e37e8a23dd59 to your computer and use it in GitHub Desktop.
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> | |
); | |
} |
halleluja!
My code works! But I still don't know why
Awesome! Thanks!
@neaumusic Thank you, it helped a lot.
Any reason, why the onblur event is on div, instead of on input element? Does it gives any advantage of placing blur event handler on one over the other?
Why would we require a set timeout event?
Thanks.
- onBlur bubbles up; put it on the outermost element needed. The
input
is there to show you can still use children with their ownonBlur
. - setTimeout(fn, 0) runs afterwards in the event loop so that
document.activeElement
refers to the newly focused element. If it's a child, we're still in the container element.
Thanks! You save my day!
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
OMG, you are a genius! Thank you so much for sharing this!
@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
Thanks for this! Just a heads up, I think you forgot to change the callbackonBlur={onBlur}
toonBlur={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!
You're my hero!
That helps a lot!
You can also use event.relatedTarget
to get the next active element on blur if you don't care about IE 11.
@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.
omg !! serveral years later , it still works!!! big thanks