3

I'm building an input component using Semantic UI React. I want it to open the dropdown whenever in focus, instead of the default behavior, which is to show results when the user changes the search string. I'm using the props available on their website here.

Here's some of my relevant code:

constructor(props) {
  super(props);
  this.handleResultSelect = this.handleResultSelect.bind(this);
  this.handleFocusSearch = this.handleFocusSearch.bind(this);
  this.handleBlurSearch = this.handleBlurSearch.bind(this);
  this.state = ({
    results: [{
      "title": "Roob, Cummerata and Watsica"
    },
    {
      "title": "Stanton, Kessler and Walsh"
    },
    {
      "title": "Boyle, Schuppe and Renner"
    }],
    value: '',
    open: false,
  });
}

handleBlurSearch () {
  this.setState({ 
    open: false,
    focused: false,
  });
}

handleFocusSearch () {
  this.setState({ 
    open: true,
    focused: true,
  });
}

handleResultSelect(e, {result}) {
  this.setState({ value: result.title });
}

render() {
  var searchProps = {
    input: <input className='custom-form-field' placeholder={this.props.placeholder}/>,
    open: this.state.open,
    onFocus: this.handleFocusSearch,
    onBlur: this.handleBlurSearch,
    results: this.state.results,
    onResultSelect: this.handleResultSelect,
    value: this.state.value,
  };

  return ( 
    <SemanticUI.Search {...searchProps} />
  );
}

However, on selecting a result, the result title value is not set in the input value. Moreover, on debugging, I found that handleResultSelect was not even being called.

My first guess is that onBlur causes the results dropdown to close, and the result select event is ignored. I'm not sure though; I'm very new to React and Semantic.

Any help figuring this out would be welcome.

GothamCityRises
  • 2,072
  • 2
  • 27
  • 43

3 Answers3

3

Try adding the value prop to the searchProps. Also, the onBlur event and onResultSelect are conflicting with each other, so I added a delay with the lodash.debounce function.

So, something like this

class SearchExampe extends React.Component {
  constructor(props) {
    super(props);
    this.handleResultSelect = this.handleResultSelect.bind(this);
    this.handleFocusSearch = this.handleFocusSearch.bind(this);
    this.handleBlurSearch = this.handleBlurSearch.bind(this);
    this.handleSearchChange = this.handleSearchChange.bind(this);

    this.state = {
      results: [
        {
          title: "Roob, Cummerata and Watsica"
        },
        {
          title: "Stanton, Kessler and Walsh"
        },
        {
          title: "Boyle, Schuppe and Renner"
        }
      ],
      value: " ",
      open: true
    };
  }

  handleSearchChange(e) {
    this.setState({ value: e.target.value });
  }

  handleBlurSearch() {
    this.setState({
      open: false,
      focused: false
    });
  }

  handleFocusSearch() {
    this.setState({
      open: true,
      focused: true
    });
  }

  handleResultSelect(e) {
    this.setState({ value: e.target.value, open: false });
  }

  render() {
    var searchProps = {
      input: <input className="custom-form-field" onChange={this.handleSearchChange} placeholder="placeholder" />,
      open: this.state.open,
      onFocus: this.handleFocusSearch,
      onBlur: _.debounce(this.handleBlurSearch, 100, {}),
      results: this.state.results,
      onResultSelect: this.handleResultSelect,
      value: this.state.value
    };

    return <Search {...searchProps} />;
  }
}
R. Wright
  • 960
  • 5
  • 9
  • Thanks for catching that; I forgot to add that line in the question originally, even though it's there. But the real problem is the inclusion of `onBlur`. Having or not having that prop is determining whether or not it works. – GothamCityRises Sep 19 '18 at 20:36
  • After some testing, delaying the onBlur function firing seems to work.. I accomplished that with `onBlur: _.debounce(this.handleBlurSearch, 500),` which uses the lodash library (`import _ from 'lodash'`). – R. Wright Sep 19 '18 at 20:49
  • Updated the example above with this. – R. Wright Sep 19 '18 at 21:10
  • That does work! Two thoughts on this, though: 1) When I'm just clicking outside the input area to close the dropdown, there will ALWAYS be a delay of `100ms` in this case, right? 2) In case there's laggy performance in the browser, there's a chance that the 100 ms timeout may not always be enough to fire the `handleResultSelect` before `onBlur`, correct? – GothamCityRises Sep 19 '18 at 21:19
  • And point 2). What if it requires more than 100 ms? Would there ever be such a scenario? In your opinion, what would be an ideal debounce time? Was testing your solution; 100ms is not enough every time. – GothamCityRises Sep 19 '18 at 21:30
  • Yes always a 100ms delay. On the order, I believe that’s unlikely, but it does look possible. More generally, we're working around an evaluation order preference of the browser. Browsers trigger the onBlur event before onClick events. – R. Wright Sep 19 '18 at 21:36
  • 1
    Some more info on the onBlur vs. onClick - https://stackoverflow.com/questions/5614773/javascript-newbie-seems-that-onblur-of-one-element-is-overriding-the-onclick/36691651 – R. Wright Sep 19 '18 at 21:45
  • As a note to anyone finding this answer now, these conflicting events were fixed in version 0.86.0 of Semantic UI React - see changelog here https://github.com/Semantic-Org/Semantic-UI-React/blob/master/CHANGELOG.md#v0860-2019-03-13 – stradled Mar 11 '20 at 16:09
1

I really appreciate R. Wright's answer, but the 200ms delay that was being added to the dropdown blur was not up to UX standards. So I dug a little deeper into javascript's blur, and found it has a relatedTarget attribute, which can be used to see what element the click was made on.

Note that, this somehow only works on DOM elements with the tabindex attribute, so I also had to modify Semantic Search's result renderer to make each result have an attribute tabindex=0. Also, it's possible to override the default focus CSS that is applied to elements with tabindex.

Using that, I edited handleBlur to set open: true if _.contains(event.relatedTarget.classList, 'title').

Here's some of my relevant code:

constructor(props) {
  super(props);
  this.handleResultSelect = this.handleResultSelect.bind(this);
  this.handleFocusSearch = this.handleFocusSearch.bind(this);
  this.handleBlurSearch = this.handleBlurSearch.bind(this);
  this.state = ({
    results: [{
      "title": "Roob, Cummerata and Watsica"
    },
    {
      "title": "Stanton, Kessler and Walsh"
    },
    {
      "title": "Boyle, Schuppe and Renner"
    }],
    value: '',
    open: false,
  });
}

handleBlurSearch (event) {
  let open = _.contains(event.relatedTarget.classList, 'title');
  this.setState({ 
    open: open,
    focused: false,
  });
}

handleFocusSearch () {
  this.setState({ 
    open: true,
    focused: true,
  });
}

handleResultSelect(e, {result}) {
  this.setState({ value: result.title, open: false });
}

render() {
  var searchProps = {
    input: <input className='custom-form-field' placeholder={this.props.placeholder}/>,
    open: this.state.open,
    onFocus: this.handleFocusSearch,
    onBlur: this.handleBlurSearch,
    results: this.state.results,
    onResultSelect: this.handleResultSelect,
  };

  return ( 
    <SemanticUI.Search {...searchProps} />
  );
}
GothamCityRises
  • 2,072
  • 2
  • 27
  • 43
0

Another option, look at the Dropdown component. The Search Selection example in the Dropdown may show the behavior that you're trying to create.

https://react.semantic-ui.com/modules/dropdown/#types-search-selection

R. Wright
  • 960
  • 5
  • 9