Tom Nolan
Tom Nolan

Reputation: 1957

How to hide a div when input or child is not focused using React?

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

Answers (3)

cookie_monster
cookie_monster

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

vbulant
vbulant

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

Chris
Chris

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

Related Questions