downshift-js / downshift

🏎 A set of primitives to build simple, flexible, WAI-ARIA compliant React autocomplete, combobox or select dropdown components.
http://downshift-js.com/
MIT License
12.09k stars 929 forks source link

onFocus support #504

Closed martinmckenna closed 6 years ago

martinmckenna commented 6 years ago

Problem description: When using the downshiftStateReducer, I'd like to be able to add a switch case for input focus

kentcdodds commented 6 years ago

Hi @martinmckenna! The reason that there isn't any support in the state reducer is because no state change occurs on input focus. If you want to respond to focus of the input then you can apply onFocus to your input like so:

<input {...getInputProps({onFocus: () => console.log('on focus')})} />

Good luck!

martinmckenna commented 6 years ago

@kentcdodds The problem that I was running into was the fact that (when in use with React), the onFocus handler was not triggering a render on Downshift's internal render method.

I need a way to change state onFocus and trigger the contents inside Downshift's render method to run

kentcdodds commented 6 years ago

What specifically do you need to have happen?

martinmckenna commented 6 years ago

I want to have the autocomplete items re-appear after something has been typed in, the field has been blurred, then the field was re-focused.

What I've tried is something like this

handleOnFocus = () => {
  this.setState({ downShiftIsOpen: true })
}

render() {
  return (
    <Downshift
      onSelect={this.handleSelectSuggestion}
      stateReducer={this.downshiftStateReducer}
      isOpen={this.state.downShiftIsOpen} 
    >
      {
        ({
          getInputProps,
          getItemProps,
          isOpen,
          inputValue,
          highlightedIndex,
        }) => {
          /*
          * The issue here is that none of the below rerenders when the 'downShiftIsOpen'
          * state changes, making it impossible to rerender autocomplete suggestions
          * when the field is getting re-focused after already being typed into previously
          */
          return (
            <div>
              <TextField
                {...getInputProps({
                  onChange: this.onNodeAddressChange,
                  placeholder: 'Enter something here...',
                  value: this.state.fieldValue
                  onFocus: this.handleOnFocus
                })}
              />
              {isOpen
                this.state.autocompleteItems.map(item => <div>{item.label}</div>)
              }
            </div>
          )
        }
      }
    </Downshift>
  )
}

And what I'd like to do is something like this

downshiftStateReducer = (state, changes) => {
  switch (changes.type) {
    // basically, don't clear the field value when we leave the field
    case Downshift.stateChangeTypes.blurInput:
    case Downshift.stateChangeTypes.mouseUp:
      return {
        ...changes,
        inputValue: state.inputValue || '',
      }
    case Downshift.stateChangeTypes.focusInput:
      return {
        ...changes,
        isOpen: true,
      }
      default:
        return changes;
  }
}

render() {
  return (
    <Downshift
      onSelect={this.handleSelectSuggestion}
      stateReducer={this.downshiftStateReducer}
    >
      {
        ({
          getInputProps,
          getItemProps,
          isOpen,
          inputValue,
          highlightedIndex,
        }) => {
          return (
            <div>
              <TextField
                {...getInputProps({
                  onChange: this.onNodeAddressChange,
                  placeholder: 'Enter something here...',
                  value: this.state.fieldValue
                })}
              />
              {isOpen
                this.state.autocompleteItems.map(item => <div>{item.label}</div>)
              }
            </div>
          )
        }
      }
    </Downshift>
  )
}
kentcdodds commented 6 years ago

Could you simply use: getInputProps({onFocus: openMenu})? The openMenu helper is documented in the README (sorry, short on time otherwise I'd make an example for you).

martinmckenna commented 6 years ago

@kentcdodds ah! yep that works like a charm. Thanks!

Definitely would help to have examples of using the action helpers in the README if you're taking requests for enhancements

kentcdodds commented 6 years ago

Here it is: https://codesandbox.io/s/github/kentcdodds/downshift-examples/tree/master/?module=%2Fsrc%2Fother-examples%2Fusing-actions.js&moduleview=1

danielrvt commented 5 years ago

@kentcdodds you make this lib fun to use, thanks!

kentcdodds commented 5 years ago

Thanks 😊

constantinpojoga commented 4 years ago

Could you simply use: getInputProps({onFocus: openMenu})? The openMenu helper is documented in the README (sorry, short on time otherwise I'd make an example for you).

@kentcdodds, @silviuaavram Thanks for the great library!!! Regarding your solution, I was fighting with it for a while. The issue with it is that on input focus, the onFocus will trigger as expected and the openMenu will do the job. The issue comes after clicking an option since the onFocus will fire one more time, it will call openMenu one more time, and onInputValueChange will not fire. Not sure if you are firing that onFocus 2 times on purpose, (which may be related to accessibility) or we need to open a bug for it.

Related to this issue, this did the job for me, (opening only if !isOpen):

onFocus: e => {
  if (!e.target.value) {
    if (!isOpen) {
      openMenu();
    }
  } else {
    e.target.select();
  }
},
meotimdihia commented 4 years ago

@kentcdodds : I have the problem is like as @constantinpojoga . I can re-create it on the example: https://codesandbox.io/s/github/kentcdodds/downshift-examples?file=/src/hooks/useCombobox/algolia-instantsearch/index.js

You can check the modified source code is forked:
https://codesandbox.io/s/gifted-morning-h9gbf

knoefel commented 2 years ago

@meotimdihia @constantinpojoga @silviuaavram I'm experiencing the same problem. Is the solution from @constantinpojoga the only option? Thx in advance!