Tutorial

How To Use Downshift in Common Dropdown Use Cases

Published on August 19, 2021
    Default avatar

    By Chris Nwamba

    How To Use Downshift in Common Dropdown Use Cases

    While we believe that this content benefits our community, we have not yet thoroughly reviewed it. If you have any suggestions for improvements, please let us know by clicking the “report an issue“ button at the bottom of the tutorial.

    Introduction

    Downshift is a library that helps you build simple, flexible, WAI-ARIA compliant enhanced input React components. Its major use case is for building autocomplete components, but it can also be used to build dropdown components.

    In this tutorial, we’ll walk through some common use cases solved with Downshift.

    Prerequisites

    To follow this tutorial, you need Node and NPM installed on your machine. A basic understanding of React will help you get the most out of this tutorial.

    If you don’t have Node.js installed, go to the Node website and install the recommended version for your operating system.

    Step 1 — Setting Up the Application

    We’ll make use of Create React App to create a simple React app with no build configuration. If you don’t have Create React App installed, run npm i create-react-app in your terminal to install it. Once you have it on your machine, run the following command to set up a new React project in a directory called downshift-examples and move to this new directory by running these commands:

    1. create-react-app downshift-examples
    2. cd downshift-examples

    Once you’re in the downshift-examples directory, run the following command to install Downshift and some other packages:

    1. yarn add downshift axios react-popper

    Open the App.css file in the src folder and add the following styles:

    src/App.css
        input {
          margin: 1rem;
          width: 20rem;
          padding: 1rem .5rem;
        }
        .downshift-dropdown {
          margin: 0 auto;
          width: 20rem;
          border: 1px solid whitesmoke;
          border-bottom: none;
        }
        .dropdown-item {
          padding: 0.5rem;
          cursor: pointer;
          border-bottom: 1px solid whitesmoke;
          font-size: 1rem;
          text-align: left;
        }
        .dropdown-button {
          padding: 0.6rem;
          border-radius: 3px;
          background: white;
          cursor: pointer;
        }
        .popper-div {
          flex: 1;
          display: flex;
          align-items: center;
          justify-content: center;
          margin-top: 5rem;
        }
        .popper-item {
          padding: .5rem;
          border-bottom: 1px solid whitesmoke;
        }
    

    With everything set up, let’s look at some basic Downshift concepts.

    Overview of Downshift Concepts

    When using Downshift, the only component we need is <Downshift />. We call the <Downshift /> component and pass it some props and it works its magic. Here are some of the most used props:

    1. onChange: This function is called when the user selects an item and the selected item has changed. It returns the selectedItem.
    2. itemToString: This function is used to determine the string value for the selected item which is used to compute the inputValue.
    3. inputValue: This represents the value the input field should have.
    4. getInputProps: This function returns the props you should apply to the input element that you render.
    5. getItemProps: This function returns the props you should apply to any menu item elements you render.
    6. isOpen: This is a boolean that indicates whether or not the menu is open.
    7. selectedItem: This represents the currently selected item input.
    8. render: This is where you render whatever you want to based on the state of Downshift. This function is called with an object.

    You can check the documentation for the full list of props. Now let’s put this knowledge to use.

    Step 2 — Creating a Select Field

    Our first Downshift use case is a select field. Go ahead and create a DownshiftOne.js file in the src folder in the root directory of your app. Add the following code to it:

    src/DownshiftOne.js
        import React from 'react'
        import Downshift from 'downshift';
    
        const books = [
          { name: 'Harry Potter' },
          { name: 'Net Moves' },
          { name: 'Half of a yellow sun' },
          { name: 'The Da Vinci Code' },
          { name: 'Born a crime' },
        ];
    
        const onChange = (selectedBook) => {
          alert(`your favourite book is ${selectedBook.name}`)
        }
    
        export default () => {
          return (
            <Downshift onChange={onChange} itemToString={books => (books ? books.name : '')}>
              {/* we'll insert a callback here */}
            </DownShift>
          )
        }
    

    In the code above, we import React and Downshift and declare an array of books, an onChange function, and also a functional component that returns the <Downshift/> component. In the <Downshift/> component, we pass the onChange and itemToString props. Inside the <Downshift/> component, we’ll pass other props to a callback and render our input field.

    Next, we’ll pass the props we need in a callback to the <Downshift/> component. Update your functional component with the following:

    src/DownshiftOne.js
        ...
    
        export default () => {
          return (
            <Downshift onChange={onChange} itemToString={books => (books ? books.name : '')}>
              // pass the downshift props into a callback
              {({ getInputProps, getItemProps, isOpen, inputValue, highlightedIndex, selectedItem, getLabelProps }) => (
                <div>
                  // add a label tag and pass our label text to the getLabelProps function
                  <label style={{ marginTop: '1rem', display: 'block' }} {...getLabelProps()}>Choose your favourite book</label> <br />
                  // add our input element and pass our placeholder to the getInputProps function
                  <input {...getInputProps({ placeholder: "Search books" })} />
                  // if the input element is open, render the div else render nothing
                  {isOpen ? (
                    <div className="downshift-dropdown">
                      {
                        // filter the books and return items that match the inputValue
                        books
                          .filter(item => !inputValue || item.name.toLowerCase().includes(inputValue.toLowerCase()))
                          // map the return value and return a div
                          .map((item, index) => (
                            <div
                              className="dropdown-item"
                              {...getItemProps({ key: item.name, index, item })}
                              style={{
                                backgroundColor: highlightedIndex === index ? 'lightgray' : 'white',
                                fontWeight: selectedItem === item ? 'bold' : 'normal',
                              }}>
                              {item.name}
                            </div>
                          ))
                      }
                    </div>
                  ) : null}
                </div>
              )}
            </Downshift>
          )
        }
    

    In the code snippet above, we passed our Downshift props as parameters to a callback:

        {({ getInputProps, getItemProps, isOpen, inputValue, highlightedIndex, selectedItem, getLabelProps }) => ()}
    

    In the callback, we add our input tag and pass it our getInputProps props:

        <input {...getInputProps({ placeholder: "Search books" })} />
    

    Next, we check if the input element is open. If it is, we return a div element containing our menu and return null if it’s not:

        { isOpen ? (<div className="downshift-dropdown">...</div>) : null }
    

    Lastly, in the div element which we’re returning, we filter through our books array, returning only the items that include the inputValue. We then map through the filtered books and render them on the page.

    We also passed the getItemProps function to the div rendered in the map function. It returns the props that we applied while rendering the items.

    Let’s import our component to the parent App component, and see our functional select field:

    src/App.js
        import React, { Component } from 'react';
        import logo from './logo.svg';
        import './App.css';
        import DownshiftOne from './DownshiftOne'; // import the component
    
        class App extends Component {
          render() {
            return (
              <div className="App">
                <header className="App-header">
                  <img src={logo} className="App-logo" alt="logo" />
                  <h1 className="App-title">Welcome to React</h1>
                </header>
                <DownshiftOne /> // render the component
              </div>
            );
          }
        }
        export default App;
    

    Ensure your server is running by running npm start in your terminal. If you open http://localhost:3000 in your browser, you will see the app running.

    Step 3 — Using Downshift with Axios

    In our next example, we’ll use Downshift to create a search field for movies. In the src folder, create a DownshiftTwo.js file and add the following code:

    src/DownshiftTwo.js
        import React, { Component } from 'react'
        import Downshift from 'downshift';
        import axios from 'axios';
    
        export default class DownshiftTwo extends Component {
          constructor(props) {
            super(props)
            this.state = {
              movies: []
            }
            this.fetchMovies = this.fetchMovies.bind(this)
            this.inputOnChange = this.inputOnChange.bind(this)
          }
          // onChange method for the input field
          inputOnChange(event) {
            if (!event.target.value) {
              return
            }
            this.fetchMovies(event.target.value)
          }
          // input field for the <Downshift /> component
          downshiftOnChange(selectedMovie) {
            alert(`your favourite movie is ${selectedMovie.title}`)
          }
          // method to fetch the movies from the movies API
          fetchMovies(movie) {
            const moviesURL = `https://api.themoviedb.org/3/search/movie?api_key=APIKey&query=${movie}`;
            axios.get(moviesURL).then(response => {
              this.setState({ movies: response.data.results })
            })
          }
          render() {
            return (
              <Downshift onChange={this.downshiftOnChange} itemToString={item => (item ? item.title : '')}>
              // pass the downshift props into a callback
                {({ selectedItem, getInputProps, getItemProps, highlightedIndex, isOpen, inputValue, getLabelProps }) => (
                  <div>
                    // add a label tag and pass our label text to the getLabelProps function
                    <label style={{ marginTop: '1rem', display: 'block' }} {...getLabelProps()}>Choose your favourite movie</label> <br />
                    // add a input tag and pass our placeholder text to the getInputProps function. We also have an onChange eventlistener on the input field
                    <input {...getInputProps({
                      placeholder: "Search movies",
                      onChange: this.inputOnChange
                    })} />
                    // if the input element is open, render the div else render nothing
                    {isOpen ? (
                      <div className="downshift-dropdown">
                        {
                          // filter the movies in the state
                          this.state.movies
                            .filter(item => !inputValue || item.title.toLowerCase().includes(inputValue.toLowerCase()))
                            .slice(0, 10) // return just the first ten. Helps improve performance
                            // map the filtered movies and display their title
                            .map((item, index) => (
                              <div
                                className="dropdown-item"
                                {...getItemProps({ key: index, index, item })}
                                style={{
                                  backgroundColor: highlightedIndex === index ? 'lightgray' : 'white',
                                  fontWeight: selectedItem === item ? 'bold' : 'normal',
                                }}>
                                {item.title}
                              </div>
                            ))
                        }
                      </div>
                    ) : null}
                  </div>
                )}
              </Downshift>
            )
          }
        }
    

    In the code above, we have a class component where we’re rendering the <Downshift/> component and passing our props to it. In the input field in the <Downshift/> component, we have an onChange event listener that listens for new input to the field.

    If there’s an input, we call the fetchMovies method which takes the value of the input field and makes an AJAX request to the movies API using Axios. We set the request response to the component state, then filter through them to return the matching item as done in the previous example.

    Import and render the component in the parent App component as we did in the previous example. Visit your browser and try searching for your favorite movie.

    Step 4 — Creating a Dropdown with Downshift

    One other use case for Downshift is powering dropdowns. Dropdown’s API helps us build simple dropdown components. Let’s create a DownshiftThree.js file in the src folder and see how to achieve this.

    In the DownshiftThree.js file, add the following code:

    src/DownshiftThree.js
        import React, { Component } from 'react'
        import Downshift from 'downshift';
    
        export default class DownshiftThree extends Component {
          constructor(props) {
            super(props)
            this.books = [
              { name: 'Harry Potter' },
              { name: 'Net Moves' },
              { name: 'Half of a yellow sun' },
              { name: 'The Da Vinci Code' },
              { name: 'Born a crime' },
            ];
    
            this.state = {
              // currently selected dropdown item
              selectedBook: ''
            }
    
            this.onChange = this.onChange.bind(this)
          }
    
          onChange(selectedBook) {
            this.setState({ selectedBook: selectedBook.name })
          }
    
          render() {
            return (
              <Downshift onChange={this.onChange} selectedItem={this.state.selectedBook} itemToString={books => (books ? books.name : '')}>
              // pass the downshift props into a callback
                {({ isOpen, getToggleButtonProps, getItemProps, highlightedIndex, selectedItem: dsSelectedItem, getLabelProps }) => (
                  <div>
                    // add a label tag and pass our label text to the getLabelProps function
                    <label style={{ marginTop: '1rem', display: 'block' }} {...getLabelProps()}>Select your favourite book</label> <br />
                    // add a button for our dropdown and pass the selected book as its content if there's a selected item
                    <button className="dropdown-button" {...getToggleButtonProps()}>
                      {this.state.selectedBook !== '' ? this.state.selectedBook : 'Select a book ...'}
                    </button>
                    <div style={{ position: 'relative' }}>
                      // if the input element is open, render the div else render nothing
                      {isOpen ? (
                        <div className="downshift-dropdown">
                          {
                            // map through all the books and render them
                            this.books.map((item, index) => (
                              <div
                                className="dropdown-item"
                                {...getItemProps({ key: index, index, item })}
                                style={{
                                  backgroundColor: highlightedIndex === index ? 'lightgray' : 'white',
                                  fontWeight: dsSelectedItem === item ? 'bold' : 'normal',
                                }}>
                                {item.name}
                              </div>
                            ))
                          }
                        </div>
                      ) : null}
                    </div>
                  </div>
                )}
              </Downshift>
            )
          }
        }
    

    In the code above, we have a DownshiftThree class component where we render the <Downshift/> component. In the callback passed to it, we have a button where we pass the getToggleButtonProps function. The button houses a ternary operator where we set the content the button based on whether the selectedBook in the component’s state is set.

    Next, we call the isOpen prop to see if the dropdown is open or not. If it is open, we map through all the books and render them in the dropdown.

    In the onChange method passed to the <Downshift/> component, whenever an item is selected, we set it the state, thereby updating the content of the button. Import and render the component to the parent App component and reload your browser to see the app at this point.

    Step 5 — Creating Forms with Downshift

    In this example, we’ll be using a Downshift input component as input fields in a form and attempting to submit the form data. In the src directory, let’s create two files: DownshiftInputField.js and DownshiftFour.js.

    In the DownshiftInputField.js, we’ll create an input component with Downshift and use it to render some input fields in the DownshiftFour.js file. Let’s create a functional component in our DownshiftInputField.js file:

    src/DownshiftInputField.js
        import React from 'react'
        import Downshift from 'downshift';
    
        export default ({ items, onChange, label, placeholder, name }) => {
          return (
            <Downshift onChange={onChange} itemToString={items => (items ? items.name : '')}>
              {({ getInputProps, getItemProps, isOpen, inputValue, highlightedIndex, selectedItem, getLabelProps }) => (
                <div>
                // add a label tag and pass our label text to the getLabelProps function
                  <label style={{ marginTop: '1rem', display: 'block' }} {...getLabelProps()}>{label}</label> <br />
                  // add an input tag and pass our placeholder text to the getInputProps function
                  <input name={name} {...getInputProps({ placeholder })} />
                  // if the input element is open, render the div else render nothing
                  {isOpen ? (
                    <div className="downshift-dropdown">
                      {
                        items
                          // filter the items and return those that includes the inputValue
                          .filter(item => !inputValue || item.name.toLowerCase().includes(inputValue.toLowerCase()))
                          // map through the returned items and render them to the page
                          .map((item, index) => (
                            <div
                              className="dropdown-item"
                              {...getItemProps({ key: item.name, index, item })}
                              style={{
                                backgroundColor: highlightedIndex === index ? 'lightgray' : 'white',
                                fontWeight: selectedItem === item ? 'bold' : 'normal',
                              }}>
                              {item.name}
                            </div>
                          ))
                      }
                    </div>
                  ) : null}
                </div>
              )}
            </Downshift>
          )
        }
    

    In the code above, our functional component takes in an array of items, an onChange function, a label and placeholder text, and finally a name. The component returns a <Downshift/> component which receives all the required props in a callback function. In the callback, we have a label and an input field.

    As with other examples, we pass the isOpen prop to a ternary operator. If the input field is open, we filter through the array of items to return items that match the inputValue, then we map through the returned items and render them to the DOM.

    Now that our input field component is ready, let’s import it into the DownshiftFour.js file:

    src/DownshiftFour.js
        import React, { Component } from 'react'
        import DownshiftInputField from './DownshiftInputField';
    
        export default class DownshiftFour extends Component {
          constructor(props) {
            super(props)
            this.state = {
              books: [
                { name: 'Harry Potter' },
                { name: 'Net Moves' },
                { name: 'Half of a yellow sun' },
                { name: 'The Da Vinci Code' },
                { name: 'Born a crime' },
              ],
              movies: [
                { name: 'Harry Potter' },
                { name: '12 Strong' },
                { name: 'Half of a yellow sun' },
                { name: 'Gringo' },
                { name: 'Black Panther' },
              ],
              book: '',
              movie: ''
            }
            this.onSubmit = this.onSubmit.bind(this);
            this.onChange = this.onChange.bind(this);
          }
    
          onSubmit(event) {
            event.preventDefault();
            alert(`
            Favourite book: ${this.state.book}
            Favourite movie: ${this.state.movie}
            has been submitted
            `)
          }
    
          onChange(selectedBook, stateAndHelpers) {
            const element = document.querySelector(`#${stateAndHelpers.id}-input`)
            this.setState({ [element.name]: selectedBook.name })
          }
    
          render() {
            return (
              <form onSubmit={this.onSubmit}>
                <DownshiftInputField
                  items={this.state.books}
                  onChange={this.onChange}
                  label="Select your favourite book"
                  name="book"
                  placeholder="Search your favourite book" />
                <DownshiftInputField
                  items={this.state.movies}
                  onChange={this.onChange}
                  label="Select your favourite movie"
                  name="movie"
                  placeholder="Search your favourite movie" />
                <input type="submit" value="Submit" className="dropdown-button" />
              </form>
            )
          }
        }
    

    In our DownshiftFour.js file, we imported our input field component and created a class component. In our component state, we have an array of books and movies and we render our input field component twice in a form: one as an input field for books and another for movies.

    Downshift’s onChange function takes in a second parameter called stateAndHelpers which gives us some information about Downshift’s current state.

    In the onChange method, we find the input field the user is currently interacting with by getting it’s id from the stateAndHelpers argument and querying the DOM for it. Once we have the element, we set it to the component state.

    When the user hits the submit button, the onSubmit method, gets the selected book and movie from the state and does whatever we want with it.

    Import and render the <DownshiftFour/> component in the parent App component and let’s give it a spin in the browser.

    Step 6 — Using Downshift with Popper.js

    In our last example for this article, we’ll be using Popper.js to change the directions of a Downshift popup. Popper is a library that makes positioning tooltips or popups a more manageable task.

    We already installed the react-popper package while setting up the application, so let’s create a DownshiftFive.js file in the src folder.

    Let’s add the following code to the new file:

    src/DownshiftFive.js
        import React, { Component } from 'react'
        import Downshift from 'downshift';
        import { Popper, Manager, Target } from 'react-popper';
    
        export default class DownshiftFive extends Component {
          // define some default props
          static defaultProps = {
            positions: [
              'top',
              'top-start',
              'top-end',
              'bottom-start',
              'bottom',
              'bottom-end',
            ]
          }
    
          render() {
            return (
              <div className="popper-div">
              // wrap the whole component in Popper's <Manager/> component
                <Manager>
                  <Downshift render={({ inputValue, getInputProps, getItemProps, isOpen, selectedItem, highlightedIndex }) => (
                    <div>
                    // wrap our input element in Popper's <Target/> component
                      <Target>
                        <input {...getInputProps({ placeholder: 'Enter a position' })} />
                      </Target>
                      <div className="downshift-dropdown">
                        {isOpen ? (
                        // pass the selected item to Popper
                          <Popper
                            placement={selectedItem || 'bottom'}
                            style={{ backgroundColor: 'skyblue' }}
                          >
                            {
                            // filter through all the positions and return the ones that include the inputValue
                              this.props.positions
                                .filter(item => !inputValue || item.includes(inputValue.toLowerCase()))
                                // map through all the returned positions and render them
                                .map((item, index) => (
                                  <div className="downshift-item popper-item"
                                    {...getItemProps({ item })}
                                    key={item}
                                    style={{
                                      cursor: 'pointer',
                                      backgroundColor: highlightedIndex === index ? '#bed5df' : 'transparent',
                                      fontWeight: selectedItem === item ? 'bold' : 'normal',
                                    }}>
                                    {item}
                                  </div>
                                ))
                            }
                          </Popper>
                        ) : null}
                      </div>
                    </div>
                  )}></Downshift>
                </Manager>
              </div>
            )
          }
        }
    

    In the code snippet above, we create a DownshiftFive class components with a default position props. These are the positions Popper will use to render our popup. In the render method of the component, we’re returning a <Downshift/> component wrapped in a <Manager/> component:

        return (
          <div className="popper-div">
            <Manager>
              <Downshift render={({ inputValue, getInputProps, getItemProps, isOpen, selectedItem, highlightedIndex }) => (
                {/* all of our remaining code goes here */}
              )}>
              </DownShift>
            </Manager>
          </div>
        )
    

    The Manager component is a wrapper exposed by Popper that needs to surround all other react-popper components on the page to make them communicate with each other.

    If you look closely, you’ll see we’re passing a render prop to the <Downshift/> component. This is another way to pass our props to the <Downshift/> component. Basically, we moved our callback and passed it to the render prop.

    In the callback passed to the render prop, we wrap our input field in a <Target/> component. This informs Popper that this is the input field around which the popup should be rendered.

    Next, we check if our input is open and render a <Popper/> component and pass our selectedItem to its placement prop. This helps Popper reposition the popup whenever a new position is selected. Lastly, as with other examples, we filter all the default prop positions, return the positions that include the inputValue, map through them and render them on the page.

    Finally, import and render the <DownshiftFive/> component in the parent App component and check it out in the browser. You will see the final product.

    Conclusion

    In this post, you’ve created an application to explore common React dropdown use cases solved with Downshift.

    Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

    Learn more about us


    About the authors
    Default avatar
    Chris Nwamba

    author

    Still looking for an answer?

    Ask a questionSearch for more help

    Was this helpful?
     
    Leave a comment
    

    This textbox defaults to using Markdown to format your answer.

    You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

    Try DigitalOcean for free

    Click below to sign up and get $200 of credit to try our products over 60 days!

    Sign up

    Join the Tech Talk
    Success! Thank you! Please check your email for further details.

    Please complete your information!

    Get our biweekly newsletter

    Sign up for Infrastructure as a Newsletter.

    Hollie's Hub for Good

    Working on improving health and education, reducing inequality, and spurring economic growth? We'd like to help.

    Become a contributor

    Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

    Welcome to the developer cloud

    DigitalOcean makes it simple to launch in the cloud and scale up as you grow — whether you're running one virtual machine or ten thousand.

    Learn more
    DigitalOcean Cloud Control Panel