Reputation: 5117
In my react application one of the component is creating a button dropdown menu like below.
<div class="dropdown">
<button class="btn btn-primary dropdown-toggle" type="button" data-toggle="dropdown">Dropdown Example
<span class="caret"></span></button>
<ul class="dropdown-menu">
<li><a onClick=doSomething href="#">HTML</a></li>
<li><a onClick=doSomething href="#">CSS</a></li>
<li><a onClick=doSomething href="#">JavaScript</a></li>
</ul>
</div>
How can I close this menu if someone clicks/hovers anywhere outside of this menu area?
Upvotes: 3
Views: 15777
Reputation: 832
onBlur = {() => console.log("Clicked outside")}
The above method is triggered when clicked outside the Dropdown. You can change the states etc to show or hide your div or react dropdown
Upvotes: 0
Reputation: 31014
Don't use jQuery events and DOM manipulations inside your React, you are missing the point.
You have a state
use it!
In the following example you can see i removed all jquery.js
and bootstrap.js
files and kept only the bootstrap.css
.
I use the state
in order to show / hide the <ul>
list (i've set it to display: block
in order to override the default display: none
of bootstrap.css
).
I have set 2 main handlers regarding showing and hiding the <ul>
and 1 handler for the onclick
of each item:
toggleShow()
- Toggle the show
property in the state
attached to the onBlur
of the button.hide()
- Hides the <ul>
via the state of course (it does one
more thing which i will explain).doSomething()
- Will fire on click of an item.This scenario has a nice challenge though, when we hide the <ul>
we can't handle the click event.
But why?
Because we don't really hide it, it doesn't get rendered when state.show
is false. So basically there's kind of a race condition of the setState
the triggers a re-render against the handler of the doSomething
event handler which can't run.
So the challenge here is to determine if active element that triggered the onBlur
event of the button is the item inside the <ul>
. If it is, then we need to let the item trigger it's onClick
event and only then set the state and "hide" the .
We did that with the help of relatedTarget
that attached to the event
. This is the active element that we are looking for, and if we have one and it's not null
then we trigger it's click event (no matter what element it is by the way as we don't care), and only then we set the state with show: false
.
A working example:
class DropDown extends React.Component{
constructor(props){
super(props);
this.state = {
show: false
}
this.doSomething = this.doSomething.bind(this);
this.toggleShow = this.toggleShow.bind(this);
this.hide = this.hide.bind(this);
}
doSomething(e){
e.preventDefault();
console.log(e.target.innerHTML);
}
toggleShow(){
this.setState({show: !this.state.show});
}
hide(e){
if(e && e.relatedTarget){
e.relatedTarget.click();
}
this.setState({show: false});
}
render(){
return(
<div className="dropdown">
<button
className="btn btn-primary dropdown-toggle"
type="button"
onClick={this.toggleShow}
onBlur={this.hide}
>
{"Dropdown Example"}
<span className="caret"></span>
</button>
{
this.state.show &&
(
<ul className="dropdown-menu" style={{display: 'block'}}>
<li><a onClick={this.doSomething} href="#">HTML</a></li>
<li><a onClick={this.doSomething} href="#">CSS</a></li>
<li><a onClick={this.doSomething} href="#">JavaScript</a></li>
</ul>
)
}
</div>
);
}
}
ReactDOM.render(<DropDown />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<link rel="stylesheet" type="text/css" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
<div id="root"></div>
Upvotes: 13
Reputation: 984
There are several ways to do this. The easiest way you can achieve this is by either having event listeners to listen to the click events or write an onBlur function for the dropdown component.
componentWillMount() {
document.body.addEventListener('click', this.handleClick);
}
componentWillUnmount() {
document.body.removeEventListener('click', this.handleClick);
}
And then you'll have to wrap your component inside the listener component.
Here is the working JS fiddle. https://jsfiddle.net/vamshikrishna144/zukcpfr2/
Upvotes: 2