3

I have a menu in my AppBar and I'd like to have to have certain items show up as buttons on the AppBar when there's space and as menu items in an existing menu when there isn't. That is, the menu is always present and always has items in it. I'd just like some functions to be AppBar buttons or menu items depending on the screen size.

Here's a simplified example:

<Menu
  id="settings-menu"
  anchorEl={anchorEl}
  open={Boolean(anchorEl)}
  onClose={handleClose}
>
  <Hidden lgUp>
    <MenuItem onClick={logout}>
      <ListItemIcon><ExitToAppIcon /></ListItemIcon>
      <ListItemText primary="Hidden log out" />
    </MenuItem>
  </Hidden>
  <MenuItem onClick={logout}>
    <ListItemIcon><ExitToAppIcon /></ListItemIcon>
    <ListItemText primary="Log out" />
  </MenuItem>
</Menu>

When I open the menu, I get an error message in the console:

Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?

Check the render method of `ForwardRef(Menu)`.
    in Hidden (at NavBar.js:76)
    in ul (created by ForwardRef(List))
    in ForwardRef(List) (created by WithStyles(ForwardRef(List)))
    in WithStyles(ForwardRef(List)) (created by ForwardRef(MenuList))
    in ForwardRef(MenuList) (created by ForwardRef(Menu))
    in div (created by ForwardRef(Paper))
    in ForwardRef(Paper) (created by WithStyles(ForwardRef(Paper)))
    in WithStyles(ForwardRef(Paper)) (created by Transition)
    in Transition (created by ForwardRef(Grow))
    in ForwardRef(Grow) (created by TrapFocus)
    in TrapFocus (created by ForwardRef(Modal))
    in div (created by ForwardRef(Modal))
    in ForwardRef(Portal) (created by ForwardRef(Modal))
    in ForwardRef(Modal) (created by ForwardRef(Popover))
    in ForwardRef(Popover) (created by WithStyles(ForwardRef(Popover)))
    in WithStyles(ForwardRef(Popover)) (created by ForwardRef(Menu))
    in ForwardRef(Menu) (created by WithStyles(ForwardRef(Menu)))
    in WithStyles(ForwardRef(Menu)) (at NavBar.js:70)
    in div (at NavBar.js:50)
    in div (created by ForwardRef(Toolbar))
    in ForwardRef(Toolbar) (created by WithStyles(ForwardRef(Toolbar)))
    in WithStyles(ForwardRef(Toolbar)) (at NavBar.js:48)
    in header (created by ForwardRef(Paper))
    in ForwardRef(Paper) (created by WithStyles(ForwardRef(Paper)))
    in WithStyles(ForwardRef(Paper)) (created by ForwardRef(AppBar))
    in ForwardRef(AppBar) (created by WithStyles(ForwardRef(AppBar)))
    in WithStyles(ForwardRef(AppBar)) (at NavBar.js:47)
    in Header (at Layout.js:32)
    in div (at Layout.js:31)
    in div (at Layout.js:29)
    in Layout (at App.js:18)
    in Unknown (at src/index.js:39)
    in Router (at src/index.js:38)
    in ThemeProvider (at src/index.js:37)
    in Provider (at src/index.js:36)

Are <Hidden> components allowed within a <Menu>? If not, what's a good way to allow menu items to appear conditionally based on the screen size?

Kyle Baley
  • 580
  • 1
  • 5
  • 16

1 Answers1

2

As the error indicates Hidden doesn't support receiving a ref and that is required for menu items.

You can, however, achieve the same thing using Box:

import React from "react";
import Button from "@material-ui/core/Button";
import Menu from "@material-ui/core/Menu";
import MenuItem from "@material-ui/core/MenuItem";
import Box from "@material-ui/core/Box";

export default function SimpleMenu() {
  const [anchorEl, setAnchorEl] = React.useState(null);

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

  const handleClose = () => {
    setAnchorEl(null);
  };

  return (
    <div>
      <Button
        aria-controls="simple-menu"
        aria-haspopup="true"
        onClick={handleClick}
      >
        Open Menu
      </Button>
      <Menu
        id="simple-menu"
        anchorEl={anchorEl}
        keepMounted
        open={Boolean(anchorEl)}
        onClose={handleClose}
      >
        <MenuItem onClick={handleClose}>Always Displayed</MenuItem>
        <Box clone display={{ sm: "none" }}>
          <MenuItem onClick={handleClose}>Profile</MenuItem>
        </Box>
        <Box clone display={{ lg: "none" }}>
          <MenuItem onClick={handleClose}>My account</MenuItem>
        </Box>
        <Box clone display={{ md: "none" }}>
          <MenuItem onClick={handleClose}>Logout</MenuItem>
        </Box>
      </Menu>
    </div>
  );
}

Edit Hidden menu items

Unfortunately, using Box can be brittle in a case like this where it is overriding a style set by the wrapped component, since import order affects which one wins (see further discussion here: Box vs className vs style for vertical spacing in Material UI).

Another option is using withStyles to create versions of MenuItem that hide at a particular breakpoint:

import React from "react";
import Button from "@material-ui/core/Button";
import Menu from "@material-ui/core/Menu";
import MenuItem from "@material-ui/core/MenuItem";
import { withStyles } from "@material-ui/core/styles";

const MenuItemHiddenLgUp = withStyles(theme => ({
  root: {
    [theme.breakpoints.up("lg")]: {
      display: "none"
    }
  }
}))(MenuItem);

export default function SimpleMenu() {
  const [anchorEl, setAnchorEl] = React.useState(null);

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

  const handleClose = () => {
    setAnchorEl(null);
  };

  return (
    <div>
      <Button
        aria-controls="simple-menu"
        aria-haspopup="true"
        onClick={handleClick}
      >
        Open Menu
      </Button>
      <Menu
        id="simple-menu"
        anchorEl={anchorEl}
        keepMounted
        open={Boolean(anchorEl)}
        onClose={handleClose}
      >
        <MenuItem onClick={handleClose}>Always Displayed</MenuItem>
        <MenuItemHiddenLgUp onClick={handleClose}>Profile</MenuItemHiddenLgUp>
        <MenuItemHiddenLgUp onClick={handleClose}>
          My account
        </MenuItemHiddenLgUp>
        <MenuItem onClick={handleClose}>Logout</MenuItem>
      </Menu>
    </div>
  );
}

Edit Hidden menu items

Documentation:

Ryan Cogswell
  • 75,046
  • 9
  • 218
  • 198
  • Thank you, this works. But during some troubleshooting, I discovered that it stops working if you swap the order of the MenuItem and Box imports, because of the ordering of the CSS from the looks of it. Is this common when working with Box? – Kyle Baley Jul 10 '20 at 16:45
  • Yes, that is a problem with `Box` -- see my answer here for more details and a discussion of alternatives: https://stackoverflow.com/questions/62562157/box-vs-classname-vs-style-for-vertical-spacing-in-material-ui/62563326#62563326. – Ryan Cogswell Jul 10 '20 at 16:48
  • @KyleBaley I've added an alternative solution (using `withStyles`) that doesn't have the brittleness of the `Box` approach. – Ryan Cogswell Jul 10 '20 at 16:59
  • Thanks @ryan-cogswell. I like that solution as well. – Kyle Baley Jul 13 '20 at 13:25