1

I suspect that my Theme is not being applied (specifically the CSS transition) to a styled component that I have wrapped in another function, however I am unsure why. I downloaded the Basic MUI Dashboard theme found here and here. I pulled this into my project and it worked just fine. To clean up the implementation of the Dashboard.tsx file I refactored the Drawer and AppBar styled components out into their own files. The code below is my implementation for the 'Drawer' in the original demo; other than wrapping this code in a function called Sidebar the code is the same and I have a similar implementation for the AppBar.

function Sidebar(open: boolean, drawerWidth: number, toggleDrawer: any){

    const Drawer = styled(MuiDrawer, { shouldForwardProp: (prop) => prop !== 'open' })(
        ({ theme, open }) => ({
            
            '& .MuiDrawer-paper': {
                position: 'relative',
                whiteSpace: 'nowrap',
                width: drawerWidth,
                transition: theme.transitions.create('width', {
                    easing: theme.transitions.easing.sharp,
                    duration: theme.transitions.duration.enteringScreen,
                }),
                boxSizing: 'border-box',
                ...(!open && {
                    overflowX: 'hidden',
                    transition: theme.transitions.create('width', {
                        easing: theme.transitions.easing.sharp,
                        duration: theme.transitions.duration.leavingScreen,
                    }),
                    width: theme.spacing(7),
                    [theme.breakpoints.up('sm')]: {
                        width: theme.spacing(9),
                    },
                }),
            },
        }),
    );

    return (
        <Drawer variant="permanent" open={open}>
            <Toolbar
                sx={{
                    display: 'flex',
                    alignItems: 'center',
                    justifyContent: 'flex-end',
                    px: [1],
                }}
            >
                <IconButton onClick={toggleDrawer}>
                    <ChevronLeftIcon />
                </IconButton>
            </Toolbar>
            <Divider />
            <List component="nav">
                {NavItems }
                <Divider sx={{ my: 1 }} />
            </List>
        </Drawer>
    );
}

export default Sidebar;

And then on the Dashboard.tsx file I have this:

<ThemeProvider theme={mdTheme}>
    //other components omitted
    {Sidebar(open, drawerWidth, toggleDrawer)}; 
    //other components omitted
</ThemeProvider>

The three props that are passed to Sidebar are from the original demo (open is a state value, drawerWidth is the width of the sidebar, and toggleDrawer is a function that updates state). When I run the run the application with these changes, the user can still toggle the drawer open and closed like in the original demo, however the smooth transition that is present in the original demo is completely gone, instead the drawer just 'slams' open and shut.

I have tried different implementations using withTheme, useTheme, etc. but nothing I have done seems to work.

One of the similar questions suggested that position:relative needed to be added to the Drawer container, but that is being applied as part of the call to styled (as far as I can tell). Any help is appreciated.

dparsons
  • 2,812
  • 5
  • 28
  • 44
  • The problem is that you are defining your `Drawer` component inside of a function (`Sidebar`) that is executed at render time, so with each render you have a new component type for your `Drawer`. You need to define your `Drawer` component at the top level. – Ryan Cogswell Apr 12 '23 at 19:22
  • If you provide a [code sandbox](https://codesandbox.io/s/new) reproducing the problem, I'll add an answer demonstrating how to fix it. – Ryan Cogswell Apr 12 '23 at 20:33
  • @RyanCogswell I don't agree with your statement that it has to be defined at the top level. [This Sandbox](https://codesandbox.io/s/fast-paper-lopc0m?file=/src/App.tsx) shows some code that achieves basically what I am looking for. However, I had to remove the styled components and add the styles directly to the Drawer component (in this example). I suppose this is fine but I still don't understand what is missing to get the `styled` component working. – dparsons Apr 13 '23 at 01:33
  • In your sandbox you are no longer defining a new component type in render -- you're now using MUI's `Drawer` which is defined at the top level. When you define the component within render, the new component type each render causes the element to be unmounted from the DOM and remounted each render which prevents any transitions from working. – Ryan Cogswell Apr 13 '23 at 02:21

1 Answers1

0

Defining your Drawer component type within the Sidebar function has the effect of creating a new component type each render. This in turn will cause the Drawer to be unmounted from the DOM and remounted as a brand new element in the DOM with each re-render -- this prevents any transitions from working.

It looks like the only reason you are defining it inside of Sidebar is to access drawerWidth. You can solve the issue by moving your styled Drawer to the top level and passing drawerWidth as a prop as shown in the working example below:

import { styled } from "@mui/material/styles";
import MuiDrawer, { DrawerProps } from "@mui/material/Drawer";

interface CustomDrawerProps extends DrawerProps {
  drawerWidth: number;
}
const Drawer = styled(MuiDrawer, {
  shouldForwardProp: (prop) => prop !== "drawerWidth"
})<CustomDrawerProps>(({ theme, open, drawerWidth }) => ({
  "& .MuiDrawer-paper": {
    position: "relative",
    whiteSpace: "nowrap",
    width: drawerWidth,
    transition: theme.transitions.create("width", {
      easing: theme.transitions.easing.sharp,
      duration: theme.transitions.duration.enteringScreen
    }),
    boxSizing: "border-box",
    ...(!open && {
      overflowX: "hidden",
      transition: theme.transitions.create("width", {
        easing: theme.transitions.easing.sharp,
        duration: theme.transitions.duration.leavingScreen
      }),
      width: theme.spacing(7),
      [theme.breakpoints.up("sm")]: {
        width: theme.spacing(9)
      }
    })
  }
}));
function Sidebar(open: boolean, drawerWidth: number, toggleDrawer: any) {
  return (
    <Drawer variant="permanent" open={open} drawerWidth={drawerWidth}>
...

Edit styled Drawer

Related answers (similar root cause):

Related documentation: https://react.dev/learn/your-first-component#nesting-and-organizing-components

Excerpt:

Components can render other components, but you must never nest their definitions. Instead, define every component at the top level.

Ryan Cogswell
  • 75,046
  • 9
  • 218
  • 198
  • 1
    Ahh, my mistake. When you said 'define the component at the top level' I thought you were saying that they needed to be defined within the root component. Appreciate the links out to the nesting documentation and the explanation. Works well. Cheers. – dparsons Apr 13 '23 at 11:32
  • That’s why I almost always include a code sandbox in my answers. Words can be misunderstood but a working example usually clears up any misunderstanding. – Ryan Cogswell Apr 13 '23 at 12:00