Event Bubbling and useRef Hook

In Dropdown component we have created two onClick event handler. When the user click on one item with one of this event handler, the browser itself creates an event object (describes and information about the click) and the event does not stop there. This event object travels up to the next parent element. If some parent element has a click event handler on it, it is automatically invoked. Then, the event travels up to all the successive parent elements. This is referred to as event bubbling.

When a user clicks on one the items, we only update the currently selected item:

key={option.value}
className=”item”
onClick={()=>onSelectedChange(option)}
>
There is nothing inside of our code that says around clicking this item that we should close the Dropdown. But, whenever we click on an element, Dropdown closes.
This is because the user is clicking on this div with class of item , we run that on click, we update the currently selected item, the event  bubbles up, goes to the div with class of ui selection which does have an on click function tied to it
onClick={()=>setOpen(!open)}
className={`ui selection dropdown ${open?”visible active”:””}`}
>
This onClick is executed and we update our open piece of state, which causes the Dropdown to close.

On the other hand, when the Dropdown is open and the user clicks anywhere on the screen (outside it), the Dropdown that shows all the options should be closed.

Our solution:

The Dropdown can set up a manual event listener on the body element. A click on any element will bubble up to the body. And that will tell the Dropdown that something has been clicked.

After making this change we get the Dropdown to close when clicking anywhere outside the component. But a new problem appears, now the dropdown does not close when selecting an element of it.

Why stay open?

What happens is that when selecting an element, the first event listener invoked is not the one of the element itself, but rather the one that exists in the body.

The first event listener gets called (manual event listener body) closed the Dropdown :  setOpen(false)

onst onBodyClick = () => {
setOpen(false);
};
document.body.addEventListener(“click”,onBodyClick,{capture:true});
return()=>{
document.body.removeEventListener(“click”,onBodyClick,{
capture:true,
});
};
Then, the event handlers that have been wired up through react (div.ui.selection) opened the Dropdown: setOpen(!open)
onClick={()=>setOpen(!open)}
className={`ui selection dropdown ${open?”visible active”:””}`}
>

To solve this let’s think about the two scenarios we have.

Now, we are not going to try to interfere with this event object itself.

Whenever a user clicks on an element, we are going to allow that event to propagate around our entire DOM structure.

It is technically possible to cancel event bubbling, but usually that is bad practice because it can very easily break other aspects of your code.

So we are not going to try to stop the event or stop the event listener from running at all.

Instead, inside of that event listener, what we really want to do is put in some code to decide whether or not to attempt to close the Dropdown based upon what element was clicked.

To solve this problem we are going to use another hook:

useRef

This hook will allow us to identify in the body’s event listener when the event originates from the selection of an element and not perform any action.