5

I'm using react-select v2 with an async select component.

        <AsyncSelect
            cacheOptions
            components={{ Option }}
            loadOptions={this.getSearchItems}
            onInputChange={this.handleInputChange}
            placeholder="Search..."
            onKeyDown={this._handleKeyPress}
        />

How do I access the value of an option that is highlighted as a result of using the down key on keyboard or hovering with a mouse?

I would like to trigger a redirect or setState based on the highlighted option. onKeyDown only sends in the input value as event.target.value.

Here is an example from Nordstrom's website:

enter image description here

Avi Kaminetzky
  • 1,489
  • 2
  • 19
  • 42

3 Answers3

5

I don't think it's possible to do it using only react-select api, but you can create own HOC which will add this functionality to AsyncSelect.

class MyAsyncSelect extends React.Component {

  /* Select component reference can be used to get currently focused option */
  getFocusedOption() {
    return this.ref.select.select.state.focusedOption;
  }

  /* we'll store lastFocusedOption as instance variable (no reason to use state) */
  componentDidMount() {
    this.lastFocusedOption = this.getFocusedOption();
  }

  /* Select component reference can be used to check if menu is opened */
  isMenuOpen() {
    return this.ref.select.state.menuIsOpen;
  }

  /* This function will be called after each user interaction (click, keydown, mousemove).
     If menu is opened and focused value has been changed we will call onFocusedOptionChanged 
     function passed to this component using props. We do it asynchronously because onKeyDown
     event is fired before the focused option has been changed.
  */
  onUserInteracted = () => {
    Promise.resolve().then(() => {
      const focusedOption = this.getFocusedOption();
      if (this.isMenuOpen() && this.lastFocusedOption !== focusedOption) {
        this.lastFocusedOption = focusedOption;
        this.props.onFocusedOptionChanged(focusedOption);
      }
    });
  }

  /* in render we're setting onUserInteracted method as callback to different user interactions */
  render () {
    return (
      <div onMouseMove={this.onUserInteracted} onClick={this.onUserInteracted}> 
        <AsyncSelect 
          {...this.props} 
          ref={ref => this.ref = ref}
          onKeyDown={this.onUserInteracted}
        />
      </div>
    );
  }
}

Then you can use this custom component in the same way as AsyncSelect but with ability to react on focused option changes:

class App extends React.Component {

  onFocusedOptionChanged = focusedValue => console.log(focusedValue) 

  render() {
    return (
      <div>
        <MyAsyncSelect
          cacheOptions
          loadOptions={loadOptions}
          defaultOptions
          onInputChange={this.handleInputChange}
          onFocusedOptionChanged={this.onFocusedOptionChanged}
        />
      </div>
    );
  }
}

working demo: https://stackblitz.com/edit/react-z7izuy

EDIT: version with additional prop onOptionSelected which will be called when option is selected by user. https://stackblitz.com/edit/react-wpljbl

Wilhelm Olejnik
  • 2,382
  • 3
  • 14
  • 21
  • Nice! I am still not clear how to only trigger on `event.key === 'enter'`. Where do I put that check? As of now, any mouseover or keydown triggers my function. – Avi Kaminetzky Aug 03 '18 at 16:21
  • 1
    I am not sure If I understand what you mean, because in your question you wanted to `access the value of an option that is highlighted as a result of using the down key on keyboard or hovering with a mouse?`. Maybe just tell me on which events you want to react and what data should be passed when such event occur. – Wilhelm Olejnik Aug 03 '18 at 17:55
  • Sorry for lack of clarity. Each option has its own link, including the input value (which I manually pass into options), so when the user selects an option, either by clicking on an option, or by pressing 'enter' on a focused option, I want to redirect then to a unique option link. – Avi Kaminetzky Aug 03 '18 at 18:25
  • In other words, onKeyDown should take in the current focused option, not the current input value. – Avi Kaminetzky Aug 03 '18 at 18:48
  • what about mouse click on option? – Wilhelm Olejnik Aug 03 '18 at 19:11
  • 1
    Same functionality. I was able to implement by passing a custom Option component and wrapping in a tag. – Avi Kaminetzky Aug 03 '18 at 19:15
2

I solved it by using onChange props.

  1. UI/Select component

      class CustomSelect extends React.PureComponent<IProps> {
        handleChange = (selectedOption: any) => {
        if (this.props.onSelect) {
            this.props.onSelect(selectedOption);
        }
      };
    
      render() {
        const { data } = this.props;
        return (
          <Select
            onChange={this.handleChange}
            options={data}
          />
        );
      }
    

    }

    1. Parent where I used CustomSelect with react-router 4 (I have access to withRouter).

      class SelectParent extends React.Component<IProps> {
        onSelect = (option: any) => {
        const id = option.value;
        const { history } = this.props;
        history.push(`/category/${id}`);
      };
      
      render() {
        const { data } = this.props;
        return (
          <Select
          data={data}
          onSelect={this.onSelect}
        />);
        }
      }
      
      export default withRouter<any>(SelectParent);
      
2

That did the trick for me.

 handleOnChange = (value, { action }) => {
    if (action === 'select-option') {
      // do something
    }
  };


 render() {
   return (
     <Select onChange={this.handleOnChange} ... />
   )
 }