I have a container that has two children: an input field and another div
element that conditionally renders depending on the value for isActive
. The problem that I'm running into is that whenever I click on the child div
, then onBlur
is triggered.
How can I prevent this from happening? I've already tried e.stopPropagation()
as you can see below. Also, I've tried moving onBlur
and onFocus
to the container div
, but that didn't work either.
import React from "react";
import "./styles.css";
export default function App() {
const [isActive, setIsActive] = React.useState(false);
const handleClick = React.useCallback((e) => {
e.stopPropagation(); // does not work
}, []);
return (
<div
className="container"
onFocus={() => setIsActive(true)}
onBlur={() => setIsActive(false)}
>
<input />
{isActive && <div className="child" onClick={handleClick} />}
</div>
);
}
CodePudding user response:
The problem is the onBlur
handler on the input element is triggered every time the user clicks outside it. So using onBlur
event listener may not be the solution.
If you are only interested in keeping the child div
visible when the user clicks it, I propose to add a click event listener to the document
, which when called, check whether the event target is inside the container div
(parent of the input
and child div
) and hide the child div
accordingly.
export default function App() {
const [isActive, setIsActive] = useState(false);
const container = useRef();
useEffect(() => {
const handler = (event) => {
if (!container.current.contains(event.target)) {
setIsActive(false);
}
};
document.addEventListener("click", handler);
return () => {
document.removeEventListener("click", handler);
};
});
return (
<div className="container" ref={ctn}>
<input className="input" onFocus={() => setIsActive(true)} />
{isActive && <div className="child" />}
</div>
);
}
This solution will not work if you really care about the focus state of the input element. The user can move focus with tab.
CodePudding user response:
You can try the fix here
Explanation
{isActive && <div className="child" onClick={handleClick} />}
handleClick
here does not help for you, because it never gets triggered due to re-rendering, so that's why e.stopPropagation()
is not working for your case.
Another problem is onFocus
and onBlur
are only applied for activable element, so it means these events happen on input
field (not div
). You can try to replace input
with another div
for the experiment.
So how to make div
become activable element? Well, you just simply put tabindex for div
. Your code will work properly
The tabindex global attribute indicates that its element can be focused
<div
className="container"
tabindex="100" //add tabindex here
onFocus={() => setIsActive(true)}
onBlur={(e) => {
const currentTarget = e.currentTarget;
// Give browser time to focus the next element
requestAnimationFrame(() => {
// Check if the new focused element is a child of the original container
if (!currentTarget.contains(document.activeElement)) {
setIsActive(false);
}
});
}}
>
<input />
{isActive && <div className="child" />}
</div>
CodePudding user response:
I am not sure if this is what you were thinking so that when you click the outer div we set active to false ? and then if we click the block we dont want it to dissapeaer
import React from "react"; import "./styles.css";
export default function App() {
const [isActive, setIsActive] = React.useState(false);
const ref = React.useRef(null);
const focus = (e) => {
ref.current.focus();
};
const handleClick = (e) => {
e.preventDefault();
if (document.activeElement !== ref.current) {
setIsActive(false);
}
};
return (
<div className="container" onClick={handleClick}>
<div className="container-inner">
<input ref={ref} id="input" onFocus={() => setIsActive(true)} />
{isActive && <div className="child" onClick={focus} />}
</div>
</div>
);
}