Downshift: Beginner friendly docs

Created on 23 Nov 2017  Ā·  16Comments  Ā·  Source: downshift-js/downshift

I wanted to bring attention to something I’ve experienced when integrating this component into the app I’m building. I like this component a lot as it covers accessibility and gives me a lot of flexibility in styling and functionality. I’ve gone through a lot of autocompletes before I found this one, so thank you.

But perhaps a more beginner friendly documentation would be useful for people like me who are junior web developers that rely on these solutions. In particular, I couldn’t figure out when to use onStateChange and onInputValueChange properly because I kept thinking of uncontrolled components as those handled by the DOM that make use of refs, while controlled components as state based. It took time and my co-worker to point out that I was thinking about this in the wrong context.

onInputValueChange can be elaborated on and the definition of ā€œcontrolledā€ in this context can be more clear as it is done in the explanation of onStateChange. More on why they are different and how to use them.

More examples explaining how to use some of the functions, such as onOuterClick and getRootProps could also help.

With that said, thanks for building this. There are a lot of advanced topics covered like render props vs HOC and I appreciate all the links that explain these topics. I’m learning a lot in the process.

docs help wanted

Most helpful comment

Thanks for the feedback! We definitely could use improved docs. I'm also thinking that I'll make a video course for egghead.io about downshift which should help!

All 16 comments

Thanks for the feedback! We definitely could use improved docs. I'm also thinking that I'll make a video course for egghead.io about downshift which should help!

@kentcdodds Is it possible to add a quick example on how to use actions. I am specifically interested in using selectItem to set the state of the downshift programatically.

Sure! here you go

Hopefully that helps :)

@kentcdodds I got that to work as well! My issue is how do I expose the ability to select an item to a parent component. Is ref my only option?

You could use a ref, or you could also control the selectedItem state by using the selectedItem control prop. See the docs for more on that :+1:

To help clarify this answer to anyone who's come across this issue looking to use Downshift as a controlled component, here's a quick example gist https://gist.github.com/JofArnold/c03326170ebd7bb9118fd6aa3bb8aaf1. Note: untested code, but hopefully it gives you an idea

tl:dr;

<Downshift
  items={arrayOfItems}
  onChange={changeHandler} // <= you need this...
  selectedItem={selectedItem} // <= ... and this
/>

The docs confused me a bit w.r.t inputValue, defaultInputValue

[Wrote the above for some Google-fu for this issue]

Thanks for chiming in with an example!

What balance

Hi, is it possible to add optionvalue and optionlabel for the result data ? Just like we do for select ? Because for this input we only have the front value

Hi @damild15, I think you should look at the itemToString prop.

OK thanks i’ll look into it

hi @kentcdodds here is my exemple:

it's about the itemtostring again
in this exemple,
seee how my suggestion have value and label , how would i use itemTostring to show the label in front of the textbox when the user choose one from the autocomplete and use value for exemple 'afa' in the background, because i's the value that am going to grab so i can save it to the database.
who would i use itemtostring in this ? and am also getting this warming:
_(Warning: A component is changing a controlled input of type text to be uncontrolled. Input elements should not switch from controlled to uncontrolled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info: https://fb.me/react-controlled-components)_
any exemple with this will help me thanks in advance

import React from 'react';
import PropTypes from 'prop-types';
import keycode from 'keycode';
import Downshift from 'downshift';
import { withStyles } from 'material-ui/styles';
import TextField from 'material-ui/TextField';
import Paper from 'material-ui/Paper';
import { MenuItem } from 'material-ui/Menu';
import Chip from 'material-ui/Chip';

const suggestions = [
  { value: 'afa',label: 'Afghanistan' },
  { value: 'ala',label: 'Aland Islands' }, 
  { value:'bra',label: 'Brazil' }]


function renderInput(inputProps) {
  const { InputProps, classes, ref, ...other } = inputProps;

  return (
    <TextField
      InputProps={{
        inputRef: ref,
        classes: {
          root: classes.inputRoot,
        },
        ...InputProps,
      }}
      {...other}
    />
  );
}

function renderSuggestion({ suggestion, index, itemProps, highlightedIndex, selectedItem }) {
  const isHighlighted = highlightedIndex === index;
  const isSelected = (selectedItem || '').indexOf(suggestion.label) > -1;

  return (
    <MenuItem
      {...itemProps}
      key={suggestion.label}
      selected={isHighlighted}
      component="div"
      style={{
        fontWeight: isSelected ? 500 : 400,
      }}
    >
      {suggestion.label}
    </MenuItem>
  );
}
renderSuggestion.propTypes = {
  highlightedIndex: PropTypes.number,
  index: PropTypes.number,
  itemProps: PropTypes.object,
  selectedItem: PropTypes.string,
  suggestion: PropTypes.shape({ label: PropTypes.string }).isRequired,
};

function getSuggestions(inputValue) {
  let count = 0;

  return suggestions.filter(suggestion => {
    const keep =
      (!inputValue || suggestion.label.toLowerCase().indexOf(inputValue.toLowerCase()) !== -1) &&
      count < 5;

    if (keep) {
      count += 1;
    }

    return keep;
  });
}


const styles = theme => ({
  root: {
    flexGrow: 1,
    height: 250,
  },
  container: {
    flexGrow: 1,
    position: 'relative',
  },
  paper: {
    position: 'absolute',
    zIndex: 1,
    marginTop: theme.spacing.unit,
    left: 0,
    right: 0,
  },
  chip: {
    margin: `${theme.spacing.unit / 2}px ${theme.spacing.unit / 4}px`,
  },
  inputRoot: {
    flexWrap: 'wrap',
  },
});

class IntegrationDownshift extends React.Component { 
  constructor(props) {
    super(props);
    this.state = { suggestions: [], inputValue: '', fundid: '', selectedItem: [] };

  }


  itemToString = item => item.value;


  render() {


  const { classes } = this.props;

  return (
    <div className={classes.root}>
      <Downshift itemToString= { this.itemToString }>
        {({ getInputProps, getItemProps, isOpen, inputValue, selectedItem, highlightedIndex }) => (
          <div className={classes.container}>
            {renderInput({
              fullWidth: true,
              classes,
              InputProps: getInputProps({
                placeholder: 'Search a country (start with a)',
                id: 'integration-downshift-simple',
              }),
            })}
            {isOpen ? (
              <Paper className={classes.paper} square>
                {getSuggestions(inputValue).map((suggestion, index) =>
                  renderSuggestion({
                    suggestion,
                    index,
                    itemProps: getItemProps({ item: suggestion.label }),
                    highlightedIndex,
                    selectedItem,
                  }),
                )}
              </Paper>
            ) : null}
          </div>
        )}
      </Downshift>

    </div>
  );

  }
}

IntegrationDownshift.propTypes = {
  classes: PropTypes.object.isRequired,
};

export default withStyles(styles)(IntegrationDownshift);

Could you put this example in a codesandbox that we can run? It'll be much easier that way. You can start here: http://kcd.im/ds-example

hi @kentcdodds I finally figure it out :) i was passing in rendresuggestion(
itemProps: getItemProps({ item: suggestion.label }), instead of suggestion.label now i am passing the whole object of what was selected ," itemProps: getItemProps({ item: suggestion }), " .

2)Then i am passing to my Downshift itemToString={this.itemToString} onChange={this.handleChange} the itemstring you told me about earlier , and now from my onchange am getting the id of what was selected so i can save it later. here is a working example. now am trying to figure out where to put a text when no matches are found .
https://codesandbox.io/s/yjy7859p0x

Hi there.
I have a question.
How may I controll the state of menu renderingr except of isOpen?
I want to render some errors like 'no matches'. I added the trigger state for component, but didn't get, how I may use in functions.

Hi @VanillaWulf,

Maybe this example from the README will help?

I'm going to go ahead and close this issue as it's a bit stale. People can feel free to open other issues if needed.

Was this page helpful?
0 / 5 - 0 ratings