Reputation: 1957
So, I have a custom search and select component that renders an input and a results list wrapped in a parent container:
<div className='wrapper'>
<input />
<div className='results'>
<div>Result Item</div>
<div>Result Item</div>
<div>Result Item</div>
<div>Result Item</div>
<div>Result Item</div>
</div>
</div>
What I'm trying to achieve is that when a user focuses on the input
the results list div appears. I'm achieving this using a piece of state - isExpanded
. So, I use the onFocus method of the input:
<input onFocus={this.handleFocus} />
And the function:
handleFocus = () => {
this.setState({
isExpanded: true
});
}
That works great. The issue I'm having is getting it to close at the proper moment. It should only close if the user physically clicks outside of the the wrapper
div
. When I need it to remain open:
So, basically any interaction within the wrapper
div should remain open.
In order to attempt to achieve this, I've put a blur event around the wrapper:
<div className='wrapper' tabIndex='1' onBlur={this.handleBlur}> ... </div>
And then the function
handleBlur = () => {
this.setState({
isExpanded: false
});
}
The problem is, when click an item or focusing on the input takes away the focused wrapper
that event fires, as it should, but I cannot prevent it from firing when I don't want it to.
I've tried using document.activeElement
mixed with ReactDOM.findDOMNode
to compare the active element, but it is still not working. Any ideas?
Here is a basic example of what I'm trying to achieve (it uses a different function binding method, but point still stands):
class SearchAndSelect extends React.Component {
constructor() {
super();
this.state = {
isExpanded: false
}
this.handleBlur = this.handleBlur.bind(this);
this.handleFocus = this.handleFocus.bind(this);
}
handleBlur() {
this.setState({
isExpanded: false
})
}
handleFocus() {
this.setState({
isExpanded: true
})
}
render(){
return(
<div className='wrapper' tabIndex='1' onBlur={this.handleBlur}>
<input onFocus={this.handleFocus} />
{this.state.isExpanded && <div>Results</div>}
</div>
);
}
}
ReactDOM.render(<SearchAndSelect />, document.getElementById('container'));
<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>
<div id="container"></div>
Upvotes: 3
Views: 3319
Reputation: 131
You can say that you want blur to be ignored if the mouse is down within your wrapper when a blur event occurs. I've used this.ignoreBlur to indicate if blur should be ignored - I have not used state as you don't want or need this to cause a re-render. I've also included moving out of the wrapper to reset ignoreBlur in case the mouse is down you would never then get the mouse up event for the wrapper and that would leave ignoreBlur permanently true!
<div className='wrapper' onMouseDown={this.setIgnoreBlur} onMouseUp={this.clearIgnoreBlur} onMouseOut={this.clearIgnoreBlur} onBlur={this.handleBlur}>
<input />
<div className='results'>
<div>Result Item</div>
<div>Result Item</div>
<div>Result Item</div>
<div>Result Item</div>
<div>Result Item</div>
</div>
</div>
setIgnoreBlur = () => {
this.ignoreBlur = true;
}
clearIgnoreBlur = () => {
this.ignoreBlur = false;
}
handleBlur = () => {
if (this.ignoreBlur) return;
this.setState({
isExpanded: false
});
}
I updated your fiddle: https://jsfiddle.net/jwm6k66c/2474/
Upvotes: 3
Reputation: 152
This higher order component should help you: https://github.com/kentor/react-click-outside
See the example there. You need to enhance your wrapper component with it and then put this.setState({ isExpanded: false })
into handleClickOutside()
method.
Upvotes: 0
Reputation: 1829
I've done something similar and I achieved it by having a div underneath:
<div className='closer' onClick={this.handleBlur...
<div className='wrapper'...
.closer:
position: absolute
width/height:100%
Upvotes: 0