In the last part of this tutorial series we will set up a mobile responsive Navbar component that has a theme switcher and the user's profile details once they're logged in.
AppBar
On this same documentation page they have many examples of built NavBars; we will take the sample code for the App Bar with a primary search field example and modify it to suit our needs.The good news is that we finished setting up the redux store in the previous article (so we don't have to add any additional code); the bad news is the NavBar code seems rather complex at first glance, so let's walk through it together!
Breaking down our NavBar.js
1) Styles:
From our example code we copied from the documentation we see that that this particular AppBar has a search bar and a hamburger icon on the far left. We don't need the search bar or the hamber icon so we will remove the styles for those and add in additional styles for...
- theme switch (themeToggle)
- image container for the user's photo (imageContainer)
- nav ruleset to change the backgroundColor. (nav)
const useStyles = makeStyles((theme) => ({
grow: {
flexGrow: 1,
},
title: {
display: 'none',
[theme.breakpoints.up('sm')]: {
display: 'block',
},
},
sectionDesktop: {
display: 'none',
[theme.breakpoints.up('md')]: {
display: 'flex',
},
},
sectionMobile: {
display: 'flex',
[theme.breakpoints.up('md')]: {
display: 'none',
},
},
nav: {
backgroundColor: theme.palette.primary.main,
color: theme.palette.primary.contrastText
},
themeToggle: {
[theme.breakpoints.up('sm')]: {
padding: 0
}
},
imageContainer: {
maxWidth: '100%',
height: 'auto',
'& img': {
width: '2em'
}
}
}));
The rest is fairly simple; the syntax of the Material UI makeStyles hook allows you to easily set nested properties (Like in SASS/SCSS) AND set up media queries. We didn't explicitly set our own breakpoints when making a custom theme so they are inherited from the default theme.
2) Local State and Theme Switch
There are lots of state variables and functions in the example component; let's determine what they do.
Note: The example also had a submenu on desktop as well, but I chose to remove it to simplify the code. This means we deleted the following...
- const [anchorEl, setAnchorEl] = React.useState(null);
- const isMenuOpen = Boolean(anchorEl);
- handleProfileMenuOpen,handleProfileMenuClose functions
- renderMenu variable
const [mobileMoreAnchorEl, setMobileMoreAnchorEl] = React.useState(null);
To enable mobile responsiveness we will have a submenu pop up when the width of the device is too small to fit all items in the default nav bar. This will be done with the Material UI Menu component. The prop anchorEl (which takes in a DOM node) determines where the menu will appear on the screen.
When the user clicks on our mobile menu icon handleMobileMenuOpen will be called. We have a variable setup to coerce the value of mobileMoreAnchorEl to a boolean. If it's still the default null value this will evaluate to false. If there is a DOM element in mobileMoreAnchorEl then we know they clicked it and want to open the mobile menu.
const isMobileMenuOpen = Boolean(mobileMoreAnchorEl);
/*The anchor pieces of state need to either be null or have a DOM element */
const handleMobileMenuOpen = (event) => {
setMobileMoreAnchorEl(event.currentTarget);
};
const handleMobileMenuClose = () => {
setMobileMoreAnchorEl(null);
};
renderMobileMenu
This variable contains the JSX for our sub-menu on mobile; The menu is made with the Material UI menu component. We will mostly leave this menu as-is but feel free to play with the Icon / badge content to change the # of notifications or emails etc...
The one thing we want to add here is a toggle to switch the theme; luckily Material UI also has a component for that
The basic example is sufficient enough we just have to supply a few props. The most important being the checked boolean and the onChange function.
For the switch I decided true=dark mode (no particular reason) so we reach in to the redux store with useSelector and grab our theme object. If the theme.palette type is "dark" checked is true. onChange when clicked will dispatch our toggleTheme action creator we made in article 3 and voila we have a working theme button!
const { auth, theme } = useSelector((state) => state);
const renderMobileMenu = (
<Menu
anchorEl={mobileMoreAnchorEl}
anchorOrigin={{ vertical: 'top', horizontal: 'right' }}
id={mobileMenuId}
keepMounted
transformOrigin={{ vertical: 'top', horizontal: 'right' }}
open={isMobileMenuOpen}
onClose={handleMobileMenuClose}
>
<MenuItem>
<IconButton aria-label='show 1 new mails' color='inherit'>
<Badge badgeContent={1} color='secondary'>
<MailIcon />
</Badge>
</IconButton>
<p>Messages</p>
</MenuItem>
...
...
...
<MenuItem>
<IconButton aria-label='toggle dark mode'>
<Switch
color='default'
checked={theme.palette.type === 'dark'}
onChange={() => dispatch(toggleTheme())}
inputProps={{ 'aria-label': 'primary checkbox' }}
name='themeToggle'
></Switch>
</IconButton>
<p>Theme </p>
</MenuItem>
</Menu>
3) The returned JSX
Again we are mostly keeping the code from App Bar with a primary search field the only thing we add to the Navbar is the users profile picture, the switch for toggling the theme and conditional rendering dependent on the user's login status.
Here are the components wrapping the navbar...
return (
<header className={classes.grow}>
<AppBar position='static' component='div'>
<Toolbar component='nav' className={classes.nav}>
<Typography className={classes.title} variant='h6' noWrap>
Google Oauth Redux
</Typography>
<div className={classes.grow} />
...
...
...
)
Within the above components we have the following two divs that separate the content that will render in the navbar on desktop widths and mobile widths. We add the conditional rendering in there.
Note: The JSX in the sectionMobile div is JUST FOR THE ICON/BUTTON to open the submenu (see renderMobileMenu variable)
Desktop NavBar items
<div className={classes.sectionDesktop}>
{auth.user ? <>
/* ...Mail & Notification IconButtons */
<IconButton aria-label='toggle dark mode'>
<Switch
color='default'
checked={theme.palette.type === 'dark'}
onChange={() => dispatch(toggleTheme())}
inputProps={{ 'aria-label': 'primary checkbox' }}
name='themeToggle'
</Switch>
</IconButton>
<IconButton
edge='end'
aria label='account of current user'
aria-haspopup='true'
color='inherit'
>
<div className={classes.imageContainer}>
<img src={auth.user.imageUrl} alt={auth.user.givenName} />
</div>
</IconButton>
</> : <p>Not Logged in </p>}
<div/>
Mobile NavBar items
import MoreIcon from '@material-ui/icons/MoreVert';
<div className={classes.sectionMobile}>
{auth.user ? <>
<IconButton
aria-label='show more'
aria-controls={mobileMenuId}
aria-haspopup='true'
onClick={handleMobileMenuOpen}
color='inherit'
>
<MoreIcon />
</IconButton>
</>: <p>Not Logged in </p>}
</div>
Finally at the very end we throw in the variable renderMobileMenu (it's not a function, just JSX) because the menu is always being rendered (even if we're not on mobile or have not opened it) but only visible to us when we click the button that triggers a state change and causes the open prop be true.
<header>
<AppBar>
<Toolbar>
...
...
</Toolbar>
</AppBar>
{renderMobileMenu}
</header>
Done π
If you have followed this 4 part series you should now have a very reasonable starting template for bootstrapping your front-end projects!
Here's a full working version and the repo to the completed code; please let me know what you think!
(Be sure to also read the Gotchas section in the Git repo)!
Top comments (0)