1

I'm looking at the using the following example code for the frame around a small app:

https://material-ui.com/components/drawers/#MiniDrawer.js

In trying to refactor the elements into separate components I've come across an issue. Cutting and pasting the code into a brand new create-react-app app work fine. However, as soon as I try to refactor out the AppBar and the Drawer into separate components, I suspect that they lose track of each other.

My version on codesandbox.io.

I've started by extracting the useStyles into a separate file:

// useStyles.js
import { makeStyles } from '@material-ui/core/styles';

const drawerWidth = 240;

const useStyles = makeStyles(theme => ({
  root: {
    display: 'flex',
  },
  appBar: {
    zIndex: theme.zIndex.drawer + 100,
    transition: theme.transitions.create(['width', 'margin'], {
      easing: theme.transitions.easing.sharp,
      duration: theme.transitions.duration.leavingScreen,
    }),
  },
  appBarShift: {
    marginLeft: drawerWidth,
    width: `calc(100% - ${drawerWidth}px)`,
    transition: theme.transitions.create(['width', 'margin'], {
      easing: theme.transitions.easing.sharp,
      duration: theme.transitions.duration.enteringScreen,
    }),
  },
  menuButton: {
    marginRight: 36,
  },
  hide: {
    display: 'none',
  },
  drawer: {
    width: drawerWidth,
    flexShrink: 0,
    whiteSpace: 'nowrap',
  },
  drawerOpen: {
    width: drawerWidth,
    transition: theme.transitions.create('width', {
      easing: theme.transitions.easing.sharp,
      duration: theme.transitions.duration.enteringScreen,
    }),
  },
  drawerClose: {
    transition: theme.transitions.create('width', {
      easing: theme.transitions.easing.sharp,
      duration: theme.transitions.duration.leavingScreen,
    }),
    overflowX: 'hidden',
    width: theme.spacing(7) + 1,
    [theme.breakpoints.up('sm')]: {
      width: theme.spacing(9) + 1,
    },
  },
  toolbar: {
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'flex-end',
    padding: theme.spacing(0, 1),
    ...theme.mixins.toolbar,
  },
  content: {
    flexGrow: 1,
    padding: theme.spacing(3),
  },
}));

export default useStyles;

... and the AppBar and Drawers into their own files:

// MyUIAppBar.js
import React from 'react';
import clsx from 'clsx';
// ...

import { Mail, Notifications, Menu } from '@material-ui/icons';

function MyUIAppBar(props) {

  const classes = props.classes;
  const toggleDrawer = props.toggleDrawer;
  const open = props.open;

  return(
    <AppBar
    position="fixed"
    className={clsx(classes.appBar, {
      [classes.appBarShift]: open,
    })}
  >
    <Toolbar>
      <IconButton
        color="inherit"
        aria-label="open drawer"
        onClick={toggleDrawer}
        edge="start"
        className={clsx(classes.menuButton, {
          [classes.hide]: open,
        })}
      >
        <Menu />
      </IconButton>
      <Typography component="h1" variant="h6" color="inherit" noWrap className={classes.title}>
        MDS MyUI
      </Typography>

      // ...

    </Toolbar>
  </AppBar>
);
}

export default MyUIAppBar;

and

// MyUIDrawer.js
import React from 'react';
import clsx from 'clsx';
import Drawer from '@material-ui/core/Drawer';
import IconButton from '@material-ui/core/IconButton';
import { ChevronLeft, ChevronRight } from '@material-ui/icons';

function MyUIDrawer(props) {

  const classes = props.classes;
  const toggleDrawer = props.toggleDrawer;
  const open = props.open;

  return (
    <Drawer
      variant="permanent"
      className={clsx(classes.drawer, {
        [classes.drawerOpen]: open,
        [classes.drawerClose]: !open,
      })}
      classes={{
        paper: clsx({
          [classes.drawerOpen]: open,
          [classes.drawerClose]: !open,
        }),
      }}
      open={open}
      onToggle={toggleDrawer}
    >
      <div className={classes.toolbar}>
        <IconButton onClick={toggleDrawer}>
          {open ? <ChevronRight /> : <ChevronLeft />}
        </IconButton>
      </div>
      {props.children}
    </Drawer>
  );
}

export default MyUIDrawer;

and in the main App file I've had to pass theme, open, classes, and toggleDrawer in as props to both components:

// App.js
import React from 'react';
import useStyles from './styles';
import WebUIDrawer from './components/WebUIDrawer';
import WebUIAppBar from './components/WebUIAppBar';
import { useToggle } from './hooks';
import { useTheme } from '@material-ui/core/styles';
// ... various imports here

export default function App() {
  const classes = useStyles();
  const theme = useTheme();
  const [ open, toggleDrawer ] = useToggle(true);

  return (
    <div className={classes.root}>
      <CssBaseline />
      <WebUIAppBar theme={theme} open={open} classes={classes} toggleDrawer={toggleDrawer}/>
      <WebUIDrawer open={open} classes={classes} toggleDrawer={toggleDrawer} >
        <List>
          {['Eeny', 'Meeny', 'Miney', 'Mo'].map((text, index) => (
            <ListItem button key={text}>
              <ListItemIcon>{index % 2 === 0 ? <MoveToInbox /> : <Mail />}</ListItemIcon>
              <ListItemText primary={text} />
            </ListItem>
          ))}
        </List>
      </WebUIDrawer>
      <Main classes={classes}/>
    </div>
  );
}

The result sort of works, but there seems to be an issue with the layering and offset of the menu. My suspicion is that somehow the theme isn't updating between the two. Is there a better way to share the style info between the components? Would it be possible to use React context to do this? Frankly, I'm stumped. :-(

Ryan Cogswell
  • 75,046
  • 9
  • 218
  • 198
Dycey
  • 4,767
  • 5
  • 47
  • 86
  • It would be helpful if you could include a [CodeSandbox](https://codesandbox.io/s/new) that reproduces your problem. – Ryan Cogswell Oct 14 '19 at 15:21
  • Your wish is my command ;-) Hope that makes the problem clearer... https://codesandbox.io/s/thirsty-mestorf-y46x7 – Dycey Oct 14 '19 at 16:50

1 Answers1

1

The primary issue is the location of your import of useStyles. It needs to be last in order to make sure that the styles it defines are placed after the default styles in the <head>. See this related answer for details on why: Material UI v4 makeStyles exported from a single file doesn't retain the styles on refresh.

Here is a fixed version of your sandbox: https://codesandbox.io/s/fixed-usestyles-import-q0m8z.

All I did was move the useStyles import in App.js.

Ryan Cogswell
  • 75,046
  • 9
  • 218
  • 198
  • 1
    Yes, ... but I suspect that was an engineer's hammer you were using... 10 seconds to hit it, 2 years to know where to hit it. Thanks Ryan, you're a sanity saver :-) – Dycey Oct 14 '19 at 17:21