The Road to Next — your interactive course for Next.js with React

React Dropdown for Material UI (MUI)

Robin Wieruch

Material UI for React, also called MUI, does not come with a native Dropdown component. Here I want to share the dropdown component that I have used for several of my freelance projects when using Material UI. Before you can use it, you have to install its Material UI (MUI) dependencies:

text
npm install @emotion/styled @mui/material @mui/icons-material

Next follows the implementation of a Material UI Dropdown Component:

javascript
import * as React from 'react';
import styled from '@emotion/styled';
import Menu from '@mui/material/Menu';
import MenuItem from '@mui/material/MenuItem';

export const Dropdown = React.forwardRef(
  (
    {
      trigger,
      menu,
      keepOpen: keepOpenGlobal,
      isOpen: controlledIsOpen,
      onOpen: onControlledOpen,
      minWidth,
    },
    ref
  ) => {
    const [isInternalOpen, setInternalOpen] = React.useState(null);

    const isOpen = controlledIsOpen || isInternalOpen;

    let anchorRef = React.useRef(null);
    if (ref) {
      anchorRef = ref;
    }

    const handleOpen = (event) => {
      event.stopPropagation();

      if (menu.length) {
        onControlledOpen
          ? onControlledOpen(event.currentTarget)
          : setInternalOpen(event.currentTarget);
      }
    };

    const handleClose = (event) => {
      event.stopPropagation();

      if (
        anchorRef.current &&
        anchorRef.current.contains(event.target)
      ) {
        return;
      }

      handleForceClose();
    };

    const handleForceClose = () => {
      onControlledOpen
        ? onControlledOpen(null)
        : setInternalOpen(null);
    };

    const renderMenu = (menuItem, index) => {
      const { keepOpen: keepOpenLocal, ...props } = menuItem.props;

      let extraProps = {};
      if (props.menu) {
        extraProps = {
          parentMenuOpen: isOpen,
        };
      }

      return React.createElement(menuItem.type, {
        ...props,
        key: index,
        ...extraProps,
        onClick: (event) => {
          event.stopPropagation();

          if (!keepOpenGlobal && !keepOpenLocal) {
            handleClose(event);
          }

          if (menuItem.props.onClick) {
            menuItem.props.onClick(event);
          }
        },
        children: props.menu
          ? React.Children.map(props.menu, renderMenu)
          : props.children,
      });
    };

    return (
      <>
        {React.cloneElement(trigger, {
          onClick: isOpen ? handleForceClose : handleOpen,
          ref: anchorRef,
        })}

        <Menu
          PaperProps={{ sx: { minWidth: minWidth ?? 0 } }}
          anchorEl={isOpen}
          open={!!isOpen}
          onClose={handleClose}
        >
          {React.Children.map(menu, renderMenu)}
        </Menu>
      </>
    );
  }
);

export const DropdownMenuItem = styled(MenuItem)`
  display: flex;
  justify-content: space-between !important;

  & > svg {
    margin-left: 32px;
  }
`;

The usage of the MUI Dropdown compopnent looks as follows:

javascript
import * as React from 'react';
import Button from '@mui/material/Button';
import AddCircleOutlinedIcon from '@mui/icons-material/AddCircleOutlined';
import EditIcon from '@mui/icons-material/Edit';
import DeleteForeverIcon from '@mui/icons-material/DeleteForever';

import { Dropdown, DropdownMenuItem } from './dropdown';

const App = () => {
  const handleCreate = () => {
    console.log('create something');
  };

  const handleEdit = () => {
    console.log('edit something');
  };

  const handleDelete = () => {
    console.log('delete something');
  };

  return (
    <Dropdown
      keepOpen
      open={open}
      trigger={<Button>Dropdown</Button>}
      menu={[
        <DropdownMenuItem onClick={handleCreate}>
          Create <AddCircleOutlinedIcon />
        </DropdownMenuItem>,
        <DropdownMenuItem onClick={handleEdit}>
          Edit <EditIcon />
        </DropdownMenuItem>,
        <DropdownMenuItem onClick={handleDelete}>
          Delete <DeleteForeverIcon />
        </DropdownMenuItem>,
      ]}
    />
  );
};

export default App;

If you want to get to know a bit of the underlying implementation details:

If you want to get a nested dropdown menu for this dropdown component:

Optionally you can use the keepOpen property to not close the dropdown if one of its menu item’s is clicked and the minWidth property to define a width for the dropdown’s menu. If you want to turn the dropdown component into a controlled component, you can pass it isOpen and onOpen props too.

Never Miss an Article

Join 50,000+ developers getting weekly insights on full-stack engineering and AI.

AI Agentic UI Architecture React Next.js TypeScript Node.js Full-Stack Monorepos Product Engineering
Subscribe on Substack

High signal, low noise. Unsubscribe at any time.