Styled MUI Drawer not opening when variant is temporary

Viewed 916

I've created a styled MuiDrawer component so I can add some custom styling the component. I want to use the temporary variant but the Drawer is not opening. When I set the Drawer variant to permanent the Drawer does show. So it's probably the passing of the open prop that is causing the error. When I use the default Drawer component from MUI the temporary variant does work:

// demo.tsx

import * as React from 'react';
// import Drawer from '@mui/material/Drawer';
import {Drawer} from './styles';
import Button from '@mui/material/Button';

export default function TemporaryDrawer() {
  const [open, setOpen] = React.useState(false);

  const toggleDrawer = () => {
    setOpen(!open);
  };

  return (
    <>
      <Button onClick={toggleDrawer}>Toggle Drawer</Button>
      <Drawer
        variant='temporary'
        open={open}
        onClose={toggleDrawer}
      >
        <p>Drawer</p>
      </Drawer>
    </>
  );
}
// styles.tsx

import {styled} from '@mui/material';
import {Theme, CSSObject} from '@mui/material/styles';
import MuiDrawer from '@mui/material/Drawer';

const drawerWidth = 240;

const openedMixin = (theme: Theme): CSSObject => ({
  backgroundColor: 'green',
  width: drawerWidth,
  transition: theme.transitions.create('width', {
    easing: theme.transitions.easing.sharp,
    duration: theme.transitions.duration.enteringScreen,
  }),
  overflowX: 'hidden',
});

const closedMixin = (theme: Theme): CSSObject => ({
  transition: theme.transitions.create('width', {
    easing: theme.transitions.easing.sharp,
    duration: theme.transitions.duration.leavingScreen,
  }),
  overflowX: 'hidden',
  width: `calc(${theme.spacing(7)} + 1px)`,
  [theme.breakpoints.up('sm')]: {
    width: `calc(${theme.spacing(8)} + 1px)`,
  },
});

export const Drawer = styled(MuiDrawer, {shouldForwardProp: (prop) => prop !== 'open'})(
  ({theme, open}) => ({
    width: drawerWidth,
    flexShrink: 0,
    whiteSpace: 'nowrap',
    boxSizing: 'border-box',
    ...(open && {
      ...openedMixin(theme),
      '& .MuiDrawer-paper': openedMixin(theme),
    }),
    ...(!open && {
      ...closedMixin(theme),
      '& .MuiDrawer-paper': closedMixin(theme),
    }),
  }),
)

https://codesandbox.io/s/temporarydrawer-material-demo-forked-zci40?file=/demo.tsx

2 Answers

While @v1s10n_4 answer is correct, I'll explain a bit more the reason.

The purpose of the shouldForwardProp callback is to prevent the styling props created by the HOC from leaking to the DOM element leading to invalid attribute error. Your Drawer has an open prop, it's a known prop of Dialog so you don't need to be concerned about the prop is not handled properly here:

const Dialog = (props) => {
  // I know this open prop, so I'm gonna extract it here
  const { open, ...other } = props

  // and do some logic with the open prop

  // and make sure it is not passed to the Component
  return <Component {...other} />
}

However, if you pass an arbitrary prop that is not from the Dialog API, like bgcolor for example:

<Dialog bgcolor='red'

Then it will be passed down to the DOM element:

const Dialog = (props) => {
  const { open, ...other /* other includes the bgcolor prop */ } = props

  // logic...

  return <Component {...other} />
}

When you are using styled to create a styled component:

const StyledDialog = styled(Dialog)(...)
<StyledDialog bgcolor='red'

It'd look like this behind the scene:

const StyledDialog = (props) => {
  const className = getStyles(props);

  return <Dialog {...props} className={className} />
}

That's why you need to use shouldForwardProp, to filter out the style-related props (not the Dialog props) so it won't be passed down to the Dialog:

const StyledDialog = (props) => {
  const { bgcolor, ...other } = props;
  const className = getStyles(props);

  // bgcolor is filtered now.
  return <Dialog {...other} className={className} />
}

you can remove {shouldForwardProp: (prop) => prop !== 'open'} in your styled Drawer definition.

codesandbox

Related