Building a Nested Select Dropdown in React with Material UI: Part 1

nested select dropdown example

When working on a project for a client, I was asked to build a dropdown menu that could display a list of filters and their children and perform a search upon select. I figured Material UI would have something useful, but unfortunately there wasn’t a solid example of what I needed to accomplish. So, I built what I needed using a combination of Material UI components! It was not the easiest, but I managed to build a dropdown that displays the nested list dynamically, so that it will work regardless of how many lists and sub-lists are needed. Let’s get started.

Dependencies

Step 1: AppBar/Toolbar, Button, Menu

  1. It is best practice in React to organize your components into two folders: ‘Containers’ and ‘Components’. I typically keep top-level components in the ‘Containers’ folder, and their children in the ‘Components’ folder. Create these folders within the ‘src’ folder generated by ‘create-react-app’.
example ‘src’ folder contents

2. Inside of the ‘Containers’ folder, create a file ‘AppBar.js’. You can name it whatever you like, as long as it is descriptive.

3. Next, you will want to import React and the necessary components from Material UI. For now, we will start with AppBar, Toolbar, Button, Menu, and makeStyles (for styling within the component).

4. Create your styles below your imports. You will want to declare a ‘const’ variable called ‘useStyles’ that uses the imported ‘makeStyles’ function with the global ‘theme’ object for Material UI. For now, we will just define the classes and add some basic styles.

const useStyles = makeStyles((theme) => ({
root: {
flexGrow: 1,
},
menuButton: {
marginRight: theme.spacing(2),
},
menu: {
maxHeight: '50em'
},
}));

These are just some basic styles I have included for my needs. Since my AppBar is within a flex container, I assigned the root a flexGrow property. I added some spacing to the right of my menu button, and I gave my menu a max height of ‘50em’. You can change and set your styles however you choose. It may be helpful to set up the structure and then come back and add properties as you need.

5. Set up your functional component.

export default function SearchAppBar(props) {
const classes = useStyles();
const [anchorEl, setAnchorEl] = React.useState(null);

const handleClick = (event) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};

return (
<div className={classes.root}>
<AppBar
position="static"
color="transparent"
elevation={0}
>
<Toolbar>
<Button
edge="start"className={classes.menuButton}
color="inherit"
aria-label="open drawer"
aria-haspopup="true"
onClick={handleClick}>
Filters //your button content goes here
</Button>
<Menu
id="simple-menu"
className={classes.menu}
anchorEl={anchorEl}
keepMounted
open={Boolean(anchorEl)}
onClose={handleClose}
anchorReference="anchorPosition"
anchorPosition={{ top: 120, left: 425 }}
>
<FilterSelect
filters={props.filters}
filterSearch={doNavSearch}
closeMenu={handleClose}
/>
</Menu>
</Toolbar>
</AppBar>
</div>
);
};

I’ll explain this more or less line by line:

1. Assign your useStyles function to a variable, ‘classes’.

2. Create a state for your anchor element with the React useState hook. This will handle where and how your menu will be displayed relative to the button we will use to open it.

3. Create a ‘handleClick’ function for your menu button. This will set the anchor element to be the button we use to open the menu.

4. Create a ‘handleClose’ function that sets the anchor element to null. This handles closing the menu display.

5. Inside your return, you’ll need to wrap everything inside a parent div. You can give this div a class name of ‘classes.root’. This will automatically apply any style properties you place inside the ‘root’ property of your styles object.

6. You will contain everything else within the AppBar component. You can check out the AppBar API for built-in props. You’ll generally want the position of the AppBar to be fixed or static, but everything else is up to you.

7. Nested within AppBar is Toolbar. AppBar and Toolbar work in tandem to provide a comfortable space for the inner components.

8. Within Toolbar, add a Button component. Check the Button API for props. I set the edge prop to ‘start’ to place my button at the start of the AppBar. I gave it a class name of ‘classes.menuButton’ to apply my styles, set the color to inherit from its parent, set my aria-labels for accessibility, and passed in my ‘handleClick’ function. Now, when the button is clicked, the anchor element state will change from null to the button, effectively displaying the menu. Give your button a label, and end the button component.

9. After the button component (NOT nested), create your menu component with an id and class name if you need. Check the Menu API for available props. The rest of the props described are pretty important, so let’s continue to go line-by-line:

a. ‘anchorEl’: This tells the menu where to look for the anchor element. Since we set up our anchorEl with useState, we can just pass in the ‘anchorEl’ variable. It will either be ‘null’ or our button element, handled by our handler functions.

b. keepMounted: This will keep the children of the menu mounted in the DOM, allowing for a more responsive menu.

c. open: This is set to a boolean value based on the state of ‘anchorEl’. If ‘anchorEl’ is ‘null’, the menu will be closed, and vice versa.

d. anchorReference: The default for this prop is ‘anchorEl’. However, I set it to ‘anchorPosition’ for my needs, as when it was set to the default, the menu obscured my button and displayed oddly. Setting to anchor position gives you a bit more control over where the menu is displayed on the page.

e. anchorPosition: This gives a position relative to the anchor element. You can play with the numbers here and see how they affect the display of your menu. Material UI has an awesome playground for anchor position here.

10. Finally, you’ll want to place your component containing your MenuList nested within your Menu component. Mine is called ‘FilterSelect’, and most importantly here I am passing in the ‘handleClose’ function from earlier, as the inner components will be handling closing the menu. I am also passing in some data from an external API as the ‘filters’ prop, so you’ll want to pass in whatever data you’re building your nested menu off of here. Close all of your outer components (Menu, Toolbar., AppBar, parent div).

That is it for part 1! Next week, I’ll discuss dynamically building the outer menu list, and after that I will cover building the nested sub-lists and handling data selection.

Full stack developer and Flatiron graduate who recently made the jump from a career as a professional musician and audio engineer | Austin, TX