I am implementing a complex autocomplete functionality, and the point is that when the input gets focused, I should show the result box, and when the user goes to the next input or clicks anywhere on the screen, or presses Escape, then I should close the result box.
To close the result box, I'm using the onBlur event.
The problem is that inside the result box I have a button that takes the user to a more complete search dialog, but when I click it, the onBlur event is fired and prevents the click of this button to happen.
Here is my code:
import { useState } from "react";
export default function IndexPage() {
const [isOpen, setIsOpen] = useState(false);
const [text, setText] = useState("");
return (
<div>
<input
onBlur={() => setIsOpen(false)}
onFocus={() => setIsOpen(true)}
/>
{isOpen && (
<div>
something
<br />
<button
onClick={() => setText("i am clicked")}>click me</button>
</div>
)}
<br />
<input />
<br />
<p>{text}</p>
</div>
);
}
How can I make it work properly?
You can see a live example in this codesandbox
I'm using Next.js
Firstly, you need to group input and button to have a wider focusing area. I'm using div for that purpose, but onBlur and onFocus are not applied for a usual div, so we need to have tabIndex="1" which is to make that element interactive with those events.
<div
tabIndex="1"
onBlur={() => setIsOpen(false)}
onFocus={() => setIsOpen(true)}
>
</div>
Secondly, we should use onMouseDown event instead of onClick, which is to avoid focusing state on the button element.
<button onMouseDown={() => setText("i am clicked")}>
click me
</button>
The full implementation can be (https://codesandbox.io/s/strange-violet-z0u3jr)
import { useState } from "react";
export default function IndexPage() {
const [isOpen, setIsOpen] = useState(false);
const [text, setText] = useState("");
return (
<div>
<div
tabIndex="1"
onBlur={() => setIsOpen(false)}
onFocus={() => setIsOpen(true)}
>
<input />
{isOpen && (
<div>
something
<br />
<button onMouseDown={() => setText("i am clicked")}>
click me
</button>
</div>
)}
</div>
<br />
<input />
<br />
<p>{text}</p>
</div>
);
}