Part 2: Build This Cool Dropdown Menu with React, React Router and CSS
Tags: react, javascript, css, webdev
Published: 2020-09-27 14:54:51

| [Part 1](https://dev.to/cooljasonmelton/build-this-cool-dropdown-menu-with-react-react-router-and-css-39ln) | 
| --------------------------------------- |
| [Part 2](https://dev.to/cooljasonmelton/part-2-build-this-cool-dropdown-menu-with-react-react-router-and-css-5094)| 



After my last [blog](https://dev.to/cooljasonmelton/build-this-cool-dropdown-menu-with-react-react-router-and-css-39ln) about building a ~cool~ dropdown menu, I got a suggestion to write a part 2.

[Andrew Bone](https://dev.to/link2twenty) pointed out, “Generally, when making a component, the aim is to make it reusable and simple enough that it rarely needs to be revisited.”

I took this to heart. In the following, I attempt to refactor my dropdown menu to be just this: reusable and simple.

![demo gif](https://dev-to-uploads.s3.amazonaws.com/i/jazget07eufuccrhth7u.gif)

> [jump to the code on GitHub](https://github.com/cooljasonmelton/dropdown2)

# Tutorial
The goal: take the previous dropdown menu and rewrite the code so it can use data from an interchangeable JSON object to create a dropdown with the same structure and styles as the original.

### Table of Contents
* Preliminary Junk
* Mapping a JSON Object
* Dynamic Styles
* Dynamic Routes
* Conclusion

### Preliminary Junk
To start, I [duplicated the original repository](https://docs.github.com/en/free-pro-team@latest/github/creating-cloning-and-archiving-repositories/duplicating-a-repository) and created a JSON file with some dummy data.

![json file](https://dev-to-uploads.s3.amazonaws.com/i/sxxfb2bk4yyvgty3yzcc.png)

### Mapping a JSON Object
To make the component dynamic, I use `map()`. Map is common in React apps as it can transform indefinitely sized array into JSX. This abstracts components making them flexible and reusable.

The original `Menu` component has a return that looks like this:

```react
    return (
        <div className="Menu">
            <div className={"m-item m-logo"}
                onClick={() => setOpenMenu(!openMenu)}>
                Menu
            </div>
            <div className={setClassNames(1)}
                onClick={() => pushToRoute("/dashboard")}>
                Dashboard
            </div>
            <div className={setClassNames(2)}
                onClick={() => pushToRoute("/settings")}>
                Settings
            </div>
            <div className={setClassNames(3)}
                onClick={() => pushToRoute("/")}>
                Sign out
            </div>
        </div>
  );
```

You can see, each item is spelled out with its own div and properties. In the refactored version, I define a cookie cutter menu item and `map()` each menu item from JSON into it.

The new return looks like this:

```react
    return (
        <div className="Menu">
            <div className={"m-item m-logo"}
                onClick={() => setOpenMenu(!openMenu)}>
                Menu
            </div>

            {renderMenuItems(data)}

        </div>
  );
```
The first menu item remains in its own div. This item acts like a button to open the dropdown. It displays when the menu is closed and the other menu items hide behind it. 

Beneath that, I call the `renderMenuItems()` function which takes a JSON object as a parameter.

`renderMenuItems()` is complicated. I will show the whole function and then explain it piece by piece.

```react
    // render each menu item after Menu button clicked
    const renderMenuItems = data => {    
        const colorArr = ["#9b5de5", "#f15bb5", "#00BBF9"];

        let colorCounter = -1;
        return data.menu.map((item, index) => {
            
            // if counter is over 2, resets to 0
            // for colorArr bracket notation to get sequence of colors
            colorCounter < 2 ? colorCounter++ : colorCounter = 0

            // dynamic styles for each menu item 
            const itemStyle = {
                "top": `${index * 1.8}em`,
                "backgroundColor": colorArr[colorCounter]
            }

            return (
                <div className="m-item"
                    key={item.id}
                    style={openMenu ? itemStyle : null}
                    onClick={() => pushToRoute(item.route)}>
                    {item.name}
                </div>
            )
        })
    }
```

I will explain `colorArr`, `colorCounter`, and `itemStyle` in the next section about Dynamic Styles.

First, notice this line:
```react
        return data.menu.map((item, index) => {
```
I am returning a `map()` of `data`, the JSON object parameter. `map()` runs a callback on each item in an array, returning the result of that function in a new array.

`map()` can take two parameters. The first parameter is the item from the array. I labeled that `item`. The second is each item’s index, labeled `index`.

Now this part:
```react
            return (
                <div className="m-item"
                    key={item.id}
                    style={openMenu ? itemStyle : null}
                    onClick={() => pushToRoute(item.route)}>
                    {item.name}
                </div>
            )
```
I return dynamic JSX for each item in the `map()`. These will be the divs of our menu items. Each item has an `id`, `name`, and `route`.

I give each div the `m-item` classname, unchanged from the original. They get an `onClick` event that triggers `pushToRoute()`. Also the same as the original except the parameter is in the JSON object as `route`. Each gets a key of the JSON’s `id`. Finally, I display the JSON object’s `name` as text in the div.

For reference, here's one of the JSON menu items:
```json
      {
        "id": "001",
        "name": "Dashboard",
        "route": "/dashboard"
      }
```

### Dynamic Styles

CSS is responsible for the dropdown animation and styling. In my original menu, I use a function called `setClassNames()` to add classnames to the items. Then, I spell out an individual classname for each item including the color and length I wanted each to drop to.

Add classnames to trigger transitions:
```react
    // parameter num corresponds to .open-# classes
    // is assigned when Menu clicked triggering animated dropdown
    const setClassNames = num => {
        const classArr = ["m-item"];
        if (openMenu) classArr.push(`open-${num}`)
        return classArr.join(' ')
    }
```

The added classnames:
```css
.open-1{
    top: 1.8em;
    background-color: #9b5de5;
}
.open-2{
    top: 3.6em;
    background-color: #f15bb5;
}
.open-3{
    top: 5.4em;
    background-color: #00BBF9;
}
```
While this works, it is not easily reusable. Not only do I have to manually spell out each new `open-#` for each additional item, I also use a lot of extra lines of code.

Since I am using `map()` on the new menu items, I can work out the styles as I go. There are two parts to the CSS that I need to include:

1. A `top` set to a size of `1.8em` times the number item it is on the list (1.8, 3.6, 5.4, 7.2, etc.).
2. One of three color hexes set as a `background-color` (#9b5de5, #f15bb5, #00BBF9).

Take a look at renderMenuItems one more time.

```react
    // render each menu item after initial Menu button
    const renderMenuItems = data => {    
        const colorArr = ["#9b5de5", "#f15bb5", "#00BBF9"];

        let colorCounter = -1;
        return data.menu.map((item, index) => {
            
            // if counter is over 2, resets to 0
            // for colorArr bracket notation to get sequence of colors
            colorCounter < 2 ? colorCounter++ : colorCounter = 0

            // dynamic styles for each menu item 
            const itemStyle = {
                "top": `${index * 1.8}em`,
                "backgroundColor": colorArr[colorCounter]
            }

            return (
                <div className="m-item"
                    key={item.id}
                    style={openMenu ? itemStyle : null}
                    onClick={() => pushToRoute(item.route)}>
                    {item.name}
                </div>
            )
        })
    }
```

React allows you to add styles to an element as an object with properties written in the camelcase syntax of JavaScript.

Notice in `itemStyle`, I set a `top` size. I use the `map()` index parameter to dynamically increase em size as `map()` iterates through the JSON.

The `background-color` is a little trickier. I set up an array called `colorArr` with the 3 color hexes. To access these, I set up a counter called `colorCounter` that I use to access the hexes.

The `colorCounter` is initially set to -1. To contially iterate through 0, 1, and 2 until `map()` finishes, I coded this ternary:
```react
            // if counter is over 2, resets to 0
            // for colorArr bracket notation to get sequence of colors
            colorCounter < 2 ? colorCounter++ : colorCounter = 0
```
If the counter is less than 2, I add 1. If it is over 2, I reset the counter to 0. Thus, the counter will run 0, 1, 2, 0, 1, 2… for as long as the map() goes.

> *Check out Andrew Bone's [comment](https://dev.to/link2twenty/comment/15cc9) for a more succinct way of getting the `colorCounter` number sequence.*

In `itemStyle`, I set the `“backgroundColor”` to colorArr[colorCounter]. Thus the colors appear in a sequence.

Finally, I need to add the `top` and `background-color` properties to the items only when the first menu item is clicked.

As previously, when clicked, the top menu item toggles `openMenu`, a `useState` hook, between true and false.

I give each div a style property:
```react
    style={openMenu ? itemStyle : null}
```

I use a ternary here to return the object containing the new `top` and `background-color` if `openMenu` is true. If false, it receives `null`.


### Dynamic Routes
The final piece of this is to go back to my `Switch` statement in `App.js` and render the routes dynamically as well. 

I can `map()` the same JSON object in order to set up the corresponding `route` of each menu item.

```react
const App = () => {
  return (
    <BrowserRouter>
      <div className="App">
        {/* dropdown menu */}
        <Menu/>
        {/* routes */}
        <Switch>
          {/* map same data as dropdown to 
            create route for each item */}
          {data.menu.map(item =>{
            return(
              <Route key={item.id}
                exact path={item.route} 
                component={null} />
            )
          })}
        </Switch>
      </div>
    </BrowserRouter>
  );
}

```

Again, I'm missing the actual components of the the app that this menu would be applied to. If they were available, I could alter the JSON to include the component names or maybe to set up a lookup table that corresponds components to the IDs in the JSON. 

### Conclusion
It was great to revisit my code and improve on my initial ideas. Again, thanks to Andrew Bone for challenging me to try this. I feel like I created a much more flexible, reusable tool this second time around.

If you have any feedback or suggestions, please reach out. Comment or email me at `jason.melton2@gmail.com`. Regardless, thanks for reading. Best, Jason.