Building a custom dropdown menu component for React
Tags: javascript, tutorial, react
Published: 2018-05-07 18:06:41

---
title: Building a custom dropdown menu component for React
published: true
tags: javascript,tutorial,react
canonical_url: https://blog.logrocket.com/building-a-custom-dropdown-menu-component-for-react-e94f02ced4a1
---

![](https://cdn-images-1.medium.com/max/1024/1*W9T0Y0rhnfYyUHHEmjKQCw.jpeg)

It’s true that adapting an existing component into your project might not always go as smoothly as you’d like when it comes to specific requirements and styling. In such cases, building your own component might be in your best interest, considering the time that might be spent on the adaptation process.

This article will walk you through an approach that I followed in my personal project to create a custom dropdown menu component in React.

### **Visual structure**

Before diving into the technical stuff, let’s quickly look at the visual structure of the dropdown menu component and decide on the requirements.

![](https://cdn-images-1.medium.com/max/1024/1*-TnLNBE384Cy2G0bsbUZkA.jpeg)<figcaption>Visual structure of a dropdown component</figcaption>

A dropdown menu consists of four basic components:

- **header** wrapping
- **header title**
- **list** wrapping
- **list items**

The corresponding HTML could look like this:

```html
<div className="dd-wrapper">
  <div className="dd-header">
    <div className="dd-header-title"></div>
  </div>
  <ul className="dd-list">
    <li className="dd-list-item"></li>
    <li className="dd-list-item"></li>
    <li className="dd-list-item"></li>
  </ul>
</div>
```

- We need to be able to toggle the **dd-list** upon clicking on **dd-header** and close it when clicked outside the **dd-wrapper**
- We need to populate the _\<li\>_ tags automatically based on data
- We need to be able to control the header title

Before we can start to meet these requirements, we must decide whether to use functional component or class component.

### **Functional component or class component?**

Functional components became faster in the latest release of React 16.

However, it is not always possible to take advantage when you need the state definition either in the component or any of the component lifecycle hooks.

For this specific example, it is possible to implement without a state definition or lifecycle hooks, but deploying them makes things more tidy and straightforward.

Using a functional component would require passing some variables as props. When we interact with the dropdown menu, we would be changing these props. Changing parent component’s props from a child component requires passing functions from parent to child as props so that you can control the parent component’s state.

If you overdo it, things will get complicated quickly. So, there are always trade-offs that should be considered.

We are going to deploy a class component with state and lifecycle hooks, whereas we will also be using functions as props in order to control the parent state.

[![](https://cdn-images-1.medium.com/max/1024/1*2H5cnv_UzdWhAwB6KOAOhg.png)](https://logrocket.com/?cid=banner_b)

### Component relations

A parent component holds single or multiple dropdown menus and since each dropdown menu has a unique content, we need to parameterize it by passing information as props.

Let’s assume we have a dropdown menu, where we select multiple locations.

Consider the following state variable inside the parent component:

```javascript
constructor(){
  super()
  this.state = {
    location: [
      {
          id: 0,
          title: 'New York',
          selected: false,
          key: 'location'
      },
      {
        id: 1,
        title: 'Dublin',
        selected: false,
        key: 'location'
      },
      {
        id: 2,
        title: 'California',
        selected: false,
        key: 'location'
      },
      {
        id: 3,
        title: 'Istanbul',
        selected: false,
        key: 'location'
      },
      {
        id: 4,
        title: 'Izmir',
        selected: false,
        key: 'location'
      },
      {
        id: 5,
        title: 'Oslo',
        selected: false,
        key: 'location'
      }
    ]
  }
}
```

We have a unique id to use with key prop of _map_ method when populating the location array; a title for each item in the list; a boolean variable named selected in order to toggle the selected items in the list (in case of multiple selections in a dropdown menu) and a key variable.

Key variable comes in very handy for using with **setState** function. I will touch on that later.

Now let’s take a look at what we passed to **Dropdown** component as props so far and shape the component accordingly. Below you see the **Dropdown** component used in a parent component.

```jsx
<Dropdown
  title="Select location"
  list={this.state.location}
/>
```

We have a title to show and an array of data.

Before editing the **render()** method, we need a component state definition.

```javascript
constructor(props){
  super(props)
  this.state = {
    listOpen: false,
    headerTitle: this.props.title
  }
}
```

Here we have a listOpen boolean variable for toggling the menu list and a headerTitle, which is equal to title prop.

Below see the **render()** method for the current configuration along with **toggleList()** and **handleClickOutside()** methods for toggling the list and closing the list when clicked outside the **dd-wrapper** respectively.

Note that **handleClickOutside()** comes from a third party HOC (Higher order component) named [**react-onclickoutside**](https://github.com/Pomax/react-onclickoutside) **.**

Moreover, [**FontAwesome**](https://www.npmjs.com/package/react-fontawesome) is a component wrapping the font-awesome icon library.

```jsx
handleClickOutside(){
  this.setState({
    listOpen: false
  })
}
toggleList(){
  this.setState(prevState => ({
    listOpen: !prevState.listOpen
  }))
}
render(){
  const{list} = this.props
  const{listOpen, headerTitle} = this.state
  return(
    <div className="dd-wrapper">
    <div className="dd-header" onClick={() => this.toggleList()}>
        <div className="dd-header-title">{headerTitle}</div>
        {listOpen
          ? <FontAwesome name="angle-up" size="2x"/>
          : <FontAwesome name="angle-down" size="2x"/>
        }
    </div>
     {listOpen && <ul className="dd-list">
       {list.map((item) => (
         <li className="dd-list-item" key={item.id} >{item.title}</li>
        ))}
      </ul>}
    </div>
  )
}
```

With a styling applied, we get the following results.

Notice that we also deployed listOpen for toggling the arrow icon up or down by using the conditional (ternary) operator.

![](https://cdn-images-1.medium.com/max/1024/1*h01tLGftHBfRkqT_-PkWGA.jpeg)<figcaption>Dropdown menu, closed and open</figcaption>

### Controlling a parent state from a child

When you pass something as a prop to a child component, you can only use that data and cannot change it unless you deploy additional props.

Passing a function, which uses **setState** , as a prop enables you to control the other props.

What you are doing is basically calling a function, which is defined in the parent component, from your child component to trigger the **setState** , which changes the state that passed as a prop in the first place.

In the case of the dropdown menu, we need to be able to toggle the selected key for the corresponding object in the location state, when a list element is clicked.

### Control function in the parent

Defining the following function in the parent component and passing it as a prop to a child component, which is the **Dropdown** component, would enable us to control the desired information in the desired state.

```javascript
toggleSelected(id, key){
  let temp = this.state[key]
  temp[id].selected = !temp[id].selected
  this.setState({
    [key]: temp
  })
}
```

Notice that we have id and key parameters for the **toggleSelected()** function.

Remember that we defined a key/value pair named key for each object inside the location array, now it is time to utilize it.

By using the key, we can tell **toggleSelected()** function which state variable to change.

```javascript
key = "location"
//These two refers to the same state variable
- this.state.location
- this.state[key]
```

Likewise, the id tells which object to refer to in the location array variable.

Time to pass it as a prop:

```jsx
<Dropdown
  title="Select location"
  list={this.state.location}
  toggleItem={this.toggleSelected}
/>
```

Call it inside the _\<li\>_ tag:

```jsx
<li className="dd-list-item" key={item.title} onClick={() => toggleItem(item.id, item.key)}>{item.title} {item.selected && <FontAwesome name="check"/>}</li>
```

Also, notice that I have added an icon, depending on the value of item.selected, to indicate that the item is selected.

![](https://cdn-images-1.medium.com/max/1024/1*YjsDfY4LQIbmU-3OKl82Kw.jpeg)<figcaption>Multiple items selected in the list</figcaption>

### Dynamic header title

One last thing I wanted to have was a dynamic header title which changes in accordance with the number of selected items.

All we need to do is count how many of the selected key/value pairs are true and then change the headerTitle state in the **Dropdown** component accordingly.

We should update the component state when the location prop changes. In order to do so, we need to listen to prop updates through a life-cycle hook.

static getDerivedStateFromProps() is what we need.

It is a new lifecycle hook replacing the old componentWillReceiveProps() method.

Since it is a static method, it has no access to this . That would deprive us of using this.setState and this.props .

With this new method, you either return null to indicate that there is no change or you directly return the state modification. Also, since you have no access to this.props due to static method, you should store the previous props in a state and then reach them through prevState .

static getDerivedStateFromProps(nextProps, prevState)

We need to pass one more prop to **Dropdown** component to use for controlling the header title.

```jsx
<Dropdown
  titleHelper="Location"
  title="Select location"
  list={this.state.location}
  toggleItem={this.toggleSelected}
/>
```

*****

```jsx
static getDerivedStateFromProps(nextProps){
    const count = nextProps.list.filter(function(a) { return a.selected; }).length;
    console.log(count)
if(count === 0){
      return {headerTitle: nextProps.title}
    }
    else if(count === 1){
      return {headerTitle: `${count} ${nextProps.titleHelper}`}
    }
    else if(count > 1){
      return {headerTitle: `${count} ${nextProps.titleHelper}s`}
    }
  }
```

The header title changes in accordance with the number of selected items in the list is shown below:

![](https://cdn-images-1.medium.com/max/1024/1*-YgUT8_GfQQIMOSkfN_gBg.jpeg)<figcaption>Dynamic header title</figcaption>

### Conclusion

With my approach towards implementing a dropdown menu component for one of my projects, I basically kept the dropdown menu content data in the wrapping parent component and passed it as a prop. Passing a function also as a prop was the trick to control the parent state, which was eventually the data used in the **Dropdown** component.

Using static getDerivedStateFromProps() helped controlling the header title based on interaction with the dropdown menu.

### Plug: [LogRocket](http://logrocket.com), a DVR for web apps

[![](https://cdn-images-1.medium.com/max/1024/1*s_rMyo6NbrAsP-XtvBaXFg.png)](http://logrocket.com)

[LogRocket](https://logrocket.com) is a frontend logging tool that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single page apps.

Try it for free.

* * *