Downshift: How to make downshift just behave like text box if user does not want to select from selection

Created on 16 Feb 2018  路  25Comments  路  Source: downshift-js/downshift

I am trying to figure how do I make Downshift Input just behave like a text box if there are no suggestions or user wants to enter something free text and ignore suggestion .

Any thoughts ?

Most helpful comment

@kavink I think this might have solved it?? (https://codesandbox.io/s/8x167ww6kj)

<Downshift
    {...input}
+    onInputValueChange={(inputValue) => {
+      input.onChange(inputValue);
+   }}
-    onStateChange={({ inputValue }) => {
-      return input.onChange(inputValue);
-    }}
>

All 25 comments

What's awesome about downshift is that it gives you the power to render what you want.

Take a look at this CodeSandbox demo that I forked from the simple downshift example.

Inside of the render method, I added an if statement to check if there are suggestions:

<Downshift
  render={({ getInputProps }) => {
    if (items.length === 0) return <input type="text" placeholder="Plain ol' textbox" />

    return <input {...getInputProps()} />
  }}
/>

Thanks @newyork-anthonyng!

Also @kavink, there are several examples on the README and I'm pretty sure one or two of them are what you're looking for. I think it's the type ahead one.

I'm going to go ahead and close this as it's not actionable, but feel free to continue asking questions and hopefully someone watching the repo can help answer.

@kentcdodds Thanks for comments, I did look at type ahead one, But onStateChange user typing and selecting does not actually reflect in submitted form. Any thoughts how I can workaround it because as soon I leave the text box, it resets the field in fold and does not persist user option.

@newyork-anthonyng Thanks, that solves issue when there are no suggestions. Im still trying to nail down when there are suggestions but user wants to submit something else. Following Type ahead example, I have onStateChange function which does exactly as onChange. But then when I leave the box, the form value resets.

@kavink Could you provide an example of this in CodeSandbox?

When I look at the TypeAhead example on CodeSandbox, the behavior isn't as you described.
For example:

  • I type ap into the input field
  • I press tab to leave the input field
  • The selected fruit text reads ap. The input field did not reset.

@newyork-anthonyng here is an example: https://codesandbox.io/s/zlv7kmmyp

@newyork-anthonyng @kentcdodds You can try entering sssss into the fruit field , and leave click elsewhere in screen you will see the data is removed/reset from Form. (form data is in the bottom)

I gave it a quick look and I can tell you there are definitely a few ways to accomplish what you want to. But I'm afraid I don't have time at the moment to show you any of them. Just posting this to say don't lose hope! This is definitely possible (without any weird hacks).

@kentcdodds No worries, Even a pointer is enough on why would it reset field on leaving if nothing is selected. Or if you can point me in right direction.

Downshift has to make decisions and have some opinions on the user experience. One decision that it has made is to call reset when the user blurs the input. The reset function will reset the input's value to the itemToString value of the selectedItem.

Downshift exposes a few methods to override these decisions. You can add your own event handler and call event.preventDefault() which will prevent downshift's event handlers from running. You can override the prop entirely with: <input {...getInputProps({})} onBlur={() => {/* overridden */}} />. Or you can use a stateReducer or control props (check the docs).

There are several options here. The integration with final form may make things slightly challenging. Once you figure out something good, a simple example we could add to the README would be appreciated!

I hope that helps.

In the example I posted, thats what I had done. Tried to override onBlur. which seems to work in this example: redux-form https://codesandbox.io/s/k594964z13 but not in mine. Same piece of code. Anyways will keep digging through, now I know some background why its happening .

     {...getInputProps({
                name: input.name,
                onBlur: input.onBlur
              })}

@kavink I took a look at your example.

Making the change below seems to make the onBlur work as you wanted it to.

<input
    {...getInputProps({
        name: input.name,
-      onBlur: input.onBlur
+      onBlur: (e) => {
+          e.preventDefault();
+          input.onBlur(e);
+      }
    })}
/>

I think the difference between your example using react-final-form and redux-form is how they handle onBlur events.

In react-final-form, it looks like e.preventDefault is not called (see relevant lines here).
In redux-form, it looks like e.preventDefault is called (see relevant lines here).

This is just a guess; I didn't dig too much into react-final-form or redux-form.

@newyork-anthonyng Thanks a ton!!! I thought this will work : https://codesandbox.io/s/zlv7kmmyp
But looks like it does not seem to behave today ... still plowing through..

@kavink I think this might have solved it?? (https://codesandbox.io/s/8x167ww6kj)

<Downshift
    {...input}
+    onInputValueChange={(inputValue) => {
+      input.onChange(inputValue);
+   }}
-    onStateChange={({ inputValue }) => {
-      return input.onChange(inputValue);
-    }}
>

Thanks a lot!!! Yes that works.... Even I got to similar solution, but then I hit another issue , changing <input to Material UI TextField with react-popper the isOpen would never become true...I then just spend last hour or so just migrating to react-autosuggest Got that working like this with final-form so I'm good for now... @kentcdodds The sandbox from @newyork-anthonyng might be good to be posted to README. obviously instead of <input if Material ui field is used, it would make a very good example for others.... Thanks a lot ! learnt a lot more than I started debugging this...

Thanks @newyork-anthonyng!

@kavink, I'm glad you got something that will work for your use cases.

If someone wants to add a react final form example to the README that would be great. The codesandbox currently has a lot of distraction around it so if we could simplify it that would be great. Also, it's probably not good to spread the input object across <Downshift> like that considering it will apply an onChange method and who knows what else to the downshift props....

@kentcdodds I would like to work with @newyork-anthonyng to clean up the react final form example and add it to the README

Super! Thanks @austintackaberry! I'll be happy to review it when you've got a PR ready :)

@newyork-anthonyng @austintackaberry @kentcdodds based on this: https://github.com/final-form/react-final-form/issues/162 Erik created a polished example and posted in README in react-final-form: https://codesandbox.io/s/qzm43nn2mj

Awesome! Who wants to open a pull request to add this to the README?

Done!

@newyork-anthonyng What does {...input} do?

Hi @omorhefere

See below for an example of what the spread syntax does in React.

const myAttributes = {
  name: "Anthony",
  lastName: "Ng"
};
<MyComponent name={myAttributes.name} lastName={myAttributes.lastName} />

// is the same as...

<MyComponent {...myAttributes} />

Based on this thread, I will sum what worked for me.

Add these props to <Downshift>:

<Downshift
  onInputValueChange={(inputValue) => {
    this.props.input.onChange(inputValue);
  }}
  selectedItem={this.props.input.value}
>

Conditionally render the input based on the shown suggestion count:

<TextField
  InputProps={getInputProps(this.state.shownSuggestionsLength == 0 ? { value: inputValue, ...this.props.InputProps } : this.props.InputProps)}
/>

Of course, for this I need to store the length of the currently shown suggestions via setState. Since (in my project) that was only known in the render method, to avoid infinite loops, I had to do it like this:

if (shownSuggestionsLength != this.state.shownSuggestionsLength) {
  this.setState({ shownSuggestionsLength })
}

My solution:

// Apply the onBlur on input element.
// selectedItem, selectItem is from downshift.

const { onFocus, onBlur, ...inputProps } = getInputProps({
  placeholder: 'Search ...',
  onChange: (e: React.ChangeEvent<HTMLInputElement>) => {
    if (!e.target.value) {
      clearSelection();
    }
  },
  // allowing user input.
  onBlur: (e: React.ChangeEvent<HTMLInputElement>) => {
    let label = e.target.value;

    if (selectedItem) {
      if (selectedItem.label === label) {
        return;
      }
    }

    selectItem({
      label: e.target.value,
    })
  }
})
Was this page helpful?
0 / 5 - 0 ratings