I have two components, Container and Item.
- Container contains Item.
- Item contains a button and a div.
These components have the following behaviors:
- Container: When I click outside of Container it should disappear, I'm achieving this by using a custom hook that detects clicks outside of components. This works just fine.
- Item: When I click in the div which is inside Item it should disappear, I'm achieving this by setting a boolean state. This also works but the problem here is that Container also disappears.
Container
const Container = ({ setDisplay }) => {
const containerRef = useRef(null);
useClickOutside(containerRef, () => {
//code to make Container disappear that is not relevant for the issue
setDisplay(false)
});
return (
<div ref={containerRef} className='container'>
<Item />
</div>
);
};
Item
const Item = () => {
const [displayItem, setDisplayItem] = useState(false);
return (
<div>
<button onClick={() => setDisplayItem(true)}>Show Item's content</button>
{displayItem && (
<div
className='item-content'
onClick={() => setDisplayItem(false)}
/>
)}
</div>
);
};
useClickOutside
const useClickOutside = (ref, handler) => {
useEffect(() => {
const trigger = e => {
if (!(ref?.current?.contains(e.target))) handler();
}
document.addEventListener('click', trigger);
return () => document.removeEventListener('click', trigger);
}, [handler, ref])
}
Why is this happening and how can I prevent it?
Note: I have to use that hook.
Both the listeners are being attached to the bubbling phase, so the inner ones trigger first.
When the item is shown, and when it's clicked, this runs:
As a result, before the event propagates outward,
setDisplayItem(false)causes this.item-contentelement to be removed from the DOM. See here, how the parent no longer exists afterwards:You can fix this by changing
useClickOutsideto also check whether the node is connected or not. If it's not connected, the element is no longer in the DOM due to a state change and rerender - so the click that was made wasn't definitely outside theref.current, so the handler shouldn't run.