I'm trying to use Material UI's drawer component (https://material-ui.com/components/drawers/) to set up a navigation bar. As I'm using this code for the project, I'm not sure exactly how to link the href properly so the user can click on the home menu button and routes to a different page. I have used history.push in each function for different pages I have created, but not sure how to link it up.
import React, { useEffect, useState } from 'react'
import { makeStyles } from '#material-ui/core/styles';
import { Drawer, Button, List, Divider, ListItem, ListItemIcon, ListItemText, AppBar, IconButton, Toolbar, Typography, Collapse } from '#material-ui/core';
import { Link } from 'react-router-dom';
import { useHistory } from 'react-router-dom';
import { useAuth } from '../contexts/AuthContext';
import clsx from 'clsx';
import SortIcon from '#material-ui/icons/Sort';
import ExpandMoreIcon from '#material-ui/icons/ExpandMore';
import InboxIcon from '#material-ui/icons/MoveToInbox';
import MailIcon from '#material-ui/icons/Mail';
import HomeIcon from '#material-ui/icons/Home';
import LockOpenIcon from '#material-ui/icons/LockOpen'; // log in
import ExitToAppIcon from '#material-ui/icons/ExitToApp'; // sign out
import AccountCircleIcon from '#material-ui/icons/AccountCircle'; //user profile?
import GroupIcon from '#material-ui/icons/Group'; // team
import AccountBoxIcon from '#material-ui/icons/AccountBox';
import wecycle from '../images/wecycle_logo.PNG';
const useStyles = makeStyles((theme) => ({
main: {
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
/*height: "100vh",*/
fontFamily: "Roboto",
},
navbar: {
backgroundColor: "white",
},
navbarContent: {
width: "100%",
margin: "0 auto",
},
navbarTitle: {
fontSize: "2rem",
flexGrow: '2',
},
icon: {
color: 'black',
fontSize: "2rem",
},
colorText: {
color: "#5AFF3D",
},
container: {
textAlign: "center",
},
title: {
color: "#fff",
fontSize: "2.5rem",
},
list: { //NEW
width: 250,
},
fullList: {
width: 'auto',
},
wecycleLogo: {
width: "115px",
height: "50px",
}
}));
/**
* The code line 63 to 147 is composed of Material UI Drawer Component code.
* Please refer to https://material-ui.com/components/drawers/ for 'Temporary Drawer'
*
* #returns header
*/
export default function Header() {
const classes = useStyles();
const {currentUser, logout } = useAuth();
const [error, setError] = useState("");
const history = useHistory();
function aboutUsRedirect() {
history.push("/aboutUs");
}
function landingRedirect() {
history.push("/");
}
function loginRedirect() {
history.push("/login");
}
function signupRedirect() {
history.push("/signup");
}
async function handleLogout() {
setError('');
try {
await logout();
history.pushState("/");
} catch {
setError("Failed to log out");
}
}
const [state, setState] = React.useState({
right: false,
});
const toggleDrawer = (anchor, open) => (event) => {
if (event.type === 'keydown' && (event.key === 'Tab' || event.key === 'Shift')) {
return;
}
setState({ ...state, [anchor]: open });
};
const list = (anchor) => (
<div
className={clsx(classes.list, {
[classes.fullList]: anchor === 'top' || anchor === 'bottom',
})}
role="presentation"
onClick={toggleDrawer(anchor, false)}
onKeyDown={toggleDrawer(anchor, false)}
>
<List>
{['Home', 'Log In', 'Sign Up'].map((text, index) => (
<ListItem button key={text}>
<ListItemIcon>{index % 2 === 0 ? <HomeIcon /> : <MailIcon />}</ListItemIcon>
<ListItemText primary={text} />
</ListItem>
))}
</List>
<Divider />
<List>
{['Profile', 'About'].map((text, index) => (
<ListItem button key={text}>
<ListItemIcon>{index % 2 === 0 ? <AccountBoxIcon /> : <GroupIcon />}</ListItemIcon>
<ListItemText primary={text} />
</ListItem>
))}
</List>
</div>
);
return (
<div className={classes.main} id="header">
<AppBar className={classes.navbar}>
<Toolbar className={classes.navbarContent}>
<h1 className={classes.navbarTitle}>
<img src = {wecycle} className={classes.wecycleLogo} />
</h1>
<IconButton>
{['right'].map((anchor) => (
<React.Fragment key={anchor}>
<IconButton onClick={toggleDrawer(anchor, true)}>
<SortIcon className={classes.icon} />
</IconButton>
<Drawer anchor={anchor} open={state[anchor]} onClose={toggleDrawer(anchor, false)}>
{list(anchor)}
</Drawer>
</React.Fragment>
))}
</IconButton>
</Toolbar>
</AppBar>
</div>
);
}
You can attach the callbacks to the ListItem component.
<ListItem button onClick={clickHandler}>
<ListItemIcon>
<IconComponent />
</ListItemIcon>
<ListItemText primary={text} />
</ListItem>
You can optimize your item mapping by moving more data into the array being mapped.
const menuItems = [
{
text: 'Home',
icon: HomeIcon,
onClick: () => history.push("/"),
},
{
text: 'Log In',
icon: MailIcon,
onClick: () => history.push("/login"),
},
{
text: 'Sign Up',
icon: HomeIcon,
onClick: () => history.push("/signup"),
},
];
...
<List>
{menuItems.map(({ text, icon: Icon, onClick}, index) => (
<ListItem button key={text} onClick={onClick}>
<ListItemIcon>
<Icon />
</ListItemIcon>
<ListItemText primary={text} />
</ListItem>
))}
</List>
Related
import { ThemeProvider } from '#material-ui/core/styles'
import React, { useState } from 'react'
import { Link } from 'react-router-dom'
import AppBar from '#material-ui/core/AppBar'
import Toolbar from '#material-ui/core/Toolbar'
import Tabs from '#material-ui/core/Tabs'
import Tab from '#material-ui/core/Tab'
import IconButton from '#material-ui/core/IconButton'
import MenuIcon from '#material-ui/icons/Menu'
import SwipeableDrawer from '#material-ui/core/SwipeableDrawer'
import { useMediaQuery, makeStyles } from '#material-ui/core'
import { useTheme } from '#material-ui/core/styles'
import { ListItem, ListItemText } from '#material-ui/core'
import logo from './logo.png'
const linksData = [
{
id: 1,
name: 'home',
path: '/',
},
{
id: 2,
name: 'portfolio',
path: '/portfolio',
},
{
id: 3,
name: 'services',
path: '/services',
},
{
id: 4,
name: 'about',
path: '/about',
},
]
const useStyles = makeStyles((theme) => ({
root: {
flexGrow: '1',
},
contentItems: {
width: '100%',
height: '100%',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
padding: '0.5rem',
},
logo: {
width: '100%',
height: '4rem',
objectFit: 'cover',
},
logoContainer: {
flexBasis: '1',
display: 'flex',
alignItems: 'center',
},
linksContainer: {
flexBasis: '3',
},
link: {
textDecoration: 'none',
minWidth: 10,
},
}))
function App() {
const theme = useTheme()
const webView = useMediaQuery(theme.breakpoints.down('md'))
const classes = useStyles()
const [value, setValue] = useState(1)
const [openDrawer, setOpenDrawer] = useState(false)
const handleChange = (e, value) => setValue(value)
const iOS = process.browser && /iPad|iPhone|iPod/.test(navigator.userAgent)
return (
<>
<Router>
<ThemeProvider theme={theme}>
<Switch>
<Route path='/'>
<div className={classes.root}>
<AppBar>
<Toolbar>
<nav className={classes.contentItems}>
<div className={classes.logoContainer}>
<img src={logo} alt='logo' className={classes.logo} />
</div>
<Tabs value={value} onChange={handleChange}>
{linksData.map(({ path, name, id }) => {
console.log(path)
return (
<Tab
key={id}
value={id}
component={Link}
to={path.toString()}
className={classes.link}
label={name}
></Tab>
)
})}
</Tabs>
{webView && (
<IconButton onClick={() => setOpenDrawer(true)}>
<MenuIcon />
</IconButton>
)}
{openDrawer && (
<SwipeableDrawer
disableBackdropTransition={!iOS}
disableDiscovery={iOS}
open={openDrawer}
onOpen={() => setOpenDrawer(true)}
onClose={() => setOpenDrawer(false)}
>
<ListItem>
<ListItemText>hello</ListItemText>
<br />
<ListItemText>hello</ListItemText>
</ListItem>
</SwipeableDrawer>
)}
</nav>
</Toolbar>
</AppBar>
</div>
</Route>
</Switch>
</ThemeProvider>
</Router>
</>
)
}
export default App
I'm trying to implement a Swipeable Drawer, the drawer is visible, the fade out transition is smooth as well, however, the drawer does not translate smoothly, The drawer slides in and out without any transition style applied to it( which should be applied). One interesting case is that in case the "openDrawer" state is set to 'true', during initialization, the drawer translates smoothly, but beyond that, the transitions of the drawer are not smooth.
Here's a CodeSandBox link: https://codesandbox.io/s/xenodochial-ritchie-owen9
At first when I ran your sandbox and from what I can see it's running fine... I don't notice any jankiness when toggling the sidebar open/close.
BUT
As I was suspecting with the conditional rendering, when I took it out the sidebar transition is butter smooth and not nearly so "instant".
{openDrawer && (
<SwipeableDrawer
disableBackdropTransition={!iOS}
disableDiscovery={iOS}
open={openDrawer}
onOpen={() => setOpenDrawer(true)}
onClose={() => setOpenDrawer(false)}
>
<ListItem>
<ListItemText>hello</ListItemText>
<br />
<ListItemText>hello</ListItemText>
</ListItem>
</SwipeableDrawer>
)}
Remove the conditional rendering.
<SwipeableDrawer
disableBackdropTransition={!iOS}
disableDiscovery={iOS}
open={openDrawer}
onOpen={() => setOpenDrawer(true)}
onClose={() => setOpenDrawer(false)}
>
<ListItem>
<ListItemText>hello</ListItemText>
<br />
<ListItemText>hello</ListItemText>
</ListItem>
</SwipeableDrawer>
I'm using Material UI to create a navigation bar with Temporary Drawer. When the user clicks on the hamburger menu icon, I want the menu to fade-in to the screen and slide from the right.
Basically, all the functionality works, except only the button named 'RIGHT' works instead of the hamburger icon I have created beside it..
I have tried removing 'right' and replace it with the icon, but when I do that the menu comes out from left-top...
import React, { useEffect, useState } from 'react'
import { makeStyles } from '#material-ui/core/styles';
import { AppBar, IconButton, Toolbar, Typography, Collapse } from '#material-ui/core';
import SortIcon from '#material-ui/icons/Sort';
import ExpandMoreIcon from '#material-ui/icons/ExpandMore';
import { Link as Scroll } from 'react-scroll'
import clsx from 'clsx';
import Drawer from '#material-ui/core/Drawer';
import Button from '#material-ui/core/Button';
import List from '#material-ui/core/List';
import Divider from '#material-ui/core/Divider';
import ListItem from '#material-ui/core/ListItem';
import ListItemIcon from '#material-ui/core/ListItemIcon';
import ListItemText from '#material-ui/core/ListItemText';
import InboxIcon from '#material-ui/icons/MoveToInbox';
import MailIcon from '#material-ui/icons/Mail';
const useStyles = makeStyles((theme) => ({
root: {
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
height: "100vh",
fontFamily: "Roboto",
},
appbar: {
background: "none",
},
appbarWrapper: {
width: "80%",
margin: "0 auto"
},
appbarTitle: {
fontSize: "2rem",
flexGrow: '1',
},
icon: {
color: '#fff',
fontSize: "2rem",
},
colorText: {
color: "#5AFF3D",
},
container: {
textAlign: "center",
},
title: {
color: "#fff",
fontSize: "4.5rem",
},
goDown: {
color: '#5AFF3D',
fontSize: '4rem',
},
list: { //NEW
width: 250,
},
fullList: {
width: 'auto',
},
}));
export default function Header() {
const classes = useStyles();
const [checked, setChecked] = useState(false);
useEffect(() => {
setChecked(true);
}, [])
// NEW
const [state, setState] = React.useState({
right: false,
});
const toggleDrawer = (anchor, open) => (event) => {
if (event.type === 'keydown' && (event.key === 'Tab' || event.key === 'Shift')) {
return;
}
setState({ ...state, [anchor]: open });
};
const list = (anchor) => (
<div
className={clsx(classes.list, {
[classes.fullList]: anchor === 'top' || anchor === 'bottom',
})}
role="presentation"
onClick={toggleDrawer(anchor, false)}
onKeyDown={toggleDrawer(anchor, false)}
>
<List>
{['Inbox', 'Starred', 'Send email', 'Drafts'].map((text, index) => (
<ListItem button key={text}>
<ListItemIcon>{index % 2 === 0 ? <InboxIcon /> : <MailIcon />}</ListItemIcon>
<ListItemText primary={text} />
</ListItem>
))}
</List>
<Divider />
<List>
{['All mail', 'Trash', 'Spam'].map((text, index) => (
<ListItem button key={text}>
<ListItemIcon>{index % 2 === 0 ? <InboxIcon /> : <MailIcon />}</ListItemIcon>
<ListItemText primary={text} />
</ListItem>
))}
</List>
</div>
);
/*
const [anchorEl, setAnchorEl] = React.useState(null);
const open = Boolean(anchorEl);
const handleClick = (event) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
*/
return (
<div className={classes.root} id="header">
<AppBar className={classes.appbar} elevation={0}>
<Toolbar className={classes.appbarWrapper}>
<h1 className={classes.appbarTitle}>
We<span className={classes.colorText}>cycle</span>
</h1>
<IconButton>
<SortIcon className={classes.icon} /*onClick={handleClick} aria-control="fade-menu" aria-haspopup="true"*/ />
{['right'].map((anchor) => (
<React.Fragment key={anchor}>
<Button onClick={toggleDrawer(anchor, true)}>{anchor}</Button>
<Drawer anchor={anchor} open={state[anchor]} onClose={toggleDrawer(anchor, false)}>
{list(anchor)}
</Drawer>
</React.Fragment>
))}
</IconButton>
{/* <Menu
id="fade-menu"
anchorEl={anchorEl}
keepMounted
open={open}
onClose={handleClose}
TransitionComponent={Fade}
>
<MenuItem onClick={handleClose}>Profile</MenuItem>
<MenuItem onClick={handleClose}>My account</MenuItem>
<MenuItem onClick={handleClose}>Logout</MenuItem>
</Menu> */}
</Toolbar>
</AppBar>
<Collapse
in={checked} {...(checked ? { timeout: 1000 } : {})}
collapsedHeight={50}
>
<div className={classes.container}>
<h1 className={classes.title}>
Meet the <br /> <span className={classes.colorText}>Team </span>
</h1>
<Scroll to="place-to-visit" smooth={true}>
<IconButton>
<ExpandMoreIcon className={classes.goDown} />
</IconButton>
</Scroll>
</div>
</Collapse>
</div>
);
}
I've made your code work for what you need and left some comments inside. I don't know what you ideally want to make, but don't copy all the code from the MUI example which you might not fully understand.
const [anchorEl, setAnchorEl] = useState(false);
// const open = Boolean(anchorEl);
const handleDrawerOpen = () => {
setAnchorEl(true);
};
const handleDrawerClose = () => {
setAnchorEl(false);
};
Above is used to control the state of Drawer.
Following is the code that you can just copy.
import React, { useEffect, useState } from 'react'
import { makeStyles } from '#material-ui/core/styles';
import { AppBar, IconButton, Toolbar, Typography, Collapse } from '#material-ui/core';
import SortIcon from '#material-ui/icons/Sort';
import ExpandMoreIcon from '#material-ui/icons/ExpandMore';
import { Link as Scroll } from 'react-scroll'
import clsx from 'clsx';
import Drawer from '#material-ui/core/Drawer';
import Button from '#material-ui/core/Button';
import List from '#material-ui/core/List';
import Divider from '#material-ui/core/Divider';
import ListItem from '#material-ui/core/ListItem';
import ListItemIcon from '#material-ui/core/ListItemIcon';
import ListItemText from '#material-ui/core/ListItemText';
import InboxIcon from '#material-ui/icons/MoveToInbox';
import MailIcon from '#material-ui/icons/Mail';
const useStyles = makeStyles((theme) => ({
root: {
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
height: "100vh",
fontFamily: "Roboto",
},
appbar: {
background: "none",
},
appbarWrapper: {
width: "80%",
margin: "0 auto"
},
appbarTitle: {
fontSize: "2rem",
flexGrow: '1',
},
icon: {
color: '#fff',
fontSize: "2rem",
},
colorText: {
color: "#5AFF3D",
},
container: {
textAlign: "center",
},
title: {
color: "#fff",
fontSize: "4.5rem",
},
goDown: {
color: '#5AFF3D',
fontSize: '4rem',
},
list: { //NEW
width: 250,
},
fullList: {
width: 'auto',
},
}));
export default function Header() {
const classes = useStyles();
const [checked, setChecked] = useState(false);
useEffect(() => {
setChecked(true);
}, [])
// NEW
const [state, setState] = React.useState({
right: false,
});
const toggleDrawer = (anchor, open) => (event) => {
if (event.type === 'keydown' && (event.key === 'Tab' || event.key === 'Shift')) {
return;
}
setState({ ...state, [anchor]: open });
};
const list = (anchor) => (
<div
className={clsx(classes.list, {
[classes.fullList]: anchor === 'top' || anchor === 'bottom',
})}
role="presentation"
onClick={toggleDrawer(anchor, false)}
onKeyDown={toggleDrawer(anchor, false)}
>
<List>
{['Inbox', 'Starred', 'Send email', 'Drafts'].map((text, index) => (
<ListItem button key={text}>
<ListItemIcon>{index % 2 === 0 ? <InboxIcon /> : <MailIcon />}</ListItemIcon>
<ListItemText primary={text} />
</ListItem>
))}
</List>
<Divider />
<List>
{['All mail', 'Trash', 'Spam'].map((text, index) => (
<ListItem button key={text}>
<ListItemIcon>{index % 2 === 0 ? <InboxIcon /> : <MailIcon />}</ListItemIcon>
<ListItemText primary={text} />
</ListItem>
))}
</List>
</div>
);
const [anchorEl, setAnchorEl] = useState(false);
// const open = Boolean(anchorEl);
const handleDrawerOpen = () => {
setAnchorEl(true);
};
const handleDrawerClose = () => {
setAnchorEl(false);
};
return (
<div className={classes.root} id="header">
<AppBar className={classes.appbar} elevation={0}>
<Toolbar className={classes.appbarWrapper}>
<h1 className={classes.appbarTitle}>
We<span className={classes.colorText}>cycle</span>
</h1>
{/* You Don't need the map here unless you want many button to toggle one Drawer,
like the example on https://material-ui.com/components/drawers/.
and the point here for your question is to move the onClick to IconButton, and you may
not want the button inside IconButton, since it is a button already,
the inside button would make it a little ugly */}
<IconButton onClick={handleDrawerOpen} onClose={handleDrawerClose}>
<SortIcon className={classes.icon} /*onClick={handleClick} aria-control="fade-menu" aria-haspopup="true"*/ />
RIGHT{/* delete the text here if you don't need */}
</IconButton>
<Drawer anchor='right' open={anchorEl} onClose={handleDrawerClose}>
{list('right')}
</Drawer>
{/* <Menu
id="fade-menu"
anchorEl={anchorEl}
keepMounted
open={open}
onClose={handleClose}
TransitionComponent={Fade}
>
<MenuItem onClick={handleClose}>Profile</MenuItem>
<MenuItem onClick={handleClose}>My account</MenuItem>
<MenuItem onClick={handleClose}>Logout</MenuItem>
</Menu> */}
</Toolbar>
</AppBar>
<Collapse
in={checked} {...(checked ? { timeout: 1000 } : {})}
collapsedHeight={50}
>
<div className={classes.container}>
<h1 className={classes.title}>
Meet the <br /> <span className={classes.colorText}>Team </span>
</h1>
<Scroll to="place-to-visit" smooth={true}>
<IconButton>
<ExpandMoreIcon className={classes.goDown} />
</IconButton>
</Scroll>
</div>
</Collapse>
</div>
);
}
Recomandation: since you have import React, { useEffect, useState } from 'react', you may not use React.useState() but use useState() directly.
Anybody has experience or idea how to implement an edit photo feature like in LinkedIn edit photo or background in React.js?
Here's the functionalities I wanted.
Can upload image
Show image
Edit uploaded image
zoom-in/zoom-out
rotate
delete
Use react-cropper package which is a react version of cropper.js. You can perform all the functionalities you need using that. There are some changes required in this code also add rotate functionality from cropper.js.
import React, { useState, useRef } from "react";
import Cropper from "react-cropper";
import Button from "#material-ui/core/Button";
import "cropperjs/dist/cropper.css";
import Grid from '#material-ui/core/Grid';
import IconButton from '#material-ui/core/IconButton';
import Tooltip from '#material-ui/core/Tooltip';
import CheckCircleIcon from '#material-ui/icons/CheckCircle';
import ArrowForwardIcon from '#material-ui/icons/ArrowForward';
import ArrowBackIcon from '#material-ui/icons/ArrowBack';
import ArrowDownwardIcon from '#material-ui/icons/ArrowDownward';
import ArrowUpwardIcon from '#material-ui/icons/ArrowUpward';
import CancelIcon from '#material-ui/icons/Cancel';
import { makeStyles } from '#material-ui/core/styles';
const useStyles = makeStyles((theme) => ({
button: {
backgroundColor: "#2774b8",
'&:hover': {
backgroundColor: "#2774b8",
}
},
toolsCtr: {
display: 'flex',
padding: '0.5rem'
},
tooltip: {
marginRight: '0.5rem'
},
icnBtn: {
padding: '8px',
borderRadius: '3px',
backgroundColor: '#f0f8ff'
},
icn: {
fontSize: '1.5rem',
color: '#2774b8'
},
cropperCtr: {
border: '2px solid #d9e5f0'
}
}))
export default function Crop(props) {
const cropperRef = useRef();
const classes = useStyles();
const [uploadedImg, setUploadedImg] = useState(props && props.image)
const [croppedLiveUrl, setCoppedLiveUrl] = useState("");
const [croppedData, setCoppedData] = useState({ url: "", data: {} });
const selForProcessing = () => {
props.useImage(croppedLiveUrl);
}
const _onMoveCropBox = () => {
if (null !== cropperRef.current) {
setCoppedLiveUrl(cropperRef.current.cropper.getCroppedCanvas().toDataURL());
setCoppedData({ url: "", data: {} });
}
};
const _onMoveImageLeft = () => {
if (null !== cropperRef.current) {
cropperRef.current.cropper.move(-10, 0);
}
}
const _onMoveImageRight = () => {
if (null !== cropperRef.current) {
cropperRef.current.cropper.move(10, 0);
}
}
const _onMoveImageTop = () => {
if (null !== cropperRef.current) {
cropperRef.current.cropper.move(0, -10);
}
}
const _onMoveImageBottom = () => {
if (null !== cropperRef.current) {
cropperRef.current.cropper.move(0, 10);
}
}
return (
<React.Fragment>
<div className={classes.cropperCtr}>
<Grid container spacing={2}>
<Grid item xs={12} sm={12} md={12} lg={12}>
<Cropper
ref={cropperRef}
src={uploadedImg}
style={{ height: 400, width: "100%", overflow:
'scroll' }}
guides={false}
crop={() => { _onMoveCropBox() }}
crossOrigin={"true"}
autoCrop={false}
movable={true}
move={() => { _onMoveImageTop() }}
/>
</Grid>
</Grid>
<div className={classes.toolsCtr}>
<Tooltip title="Use Selection" aria-label="use" className=.
{classes.tooltip}>
<IconButton className={classes.icnBtn} onClick={() => {
selForProcessing()
}}>
<CheckCircleIcon className={classes.icn} />
</IconButton>
</Tooltip>
<Tooltip title="Move Left" aria-label="left" className=
{classes.tooltip}>
<IconButton className={classes.icnBtn} onClick={() => {
_onMoveImageLeft()
}}>
<ArrowBackIcon className={classes.icn} />
</IconButton>
</Tooltip>
<Tooltip title="Move Right" aria-label="right" className=
{classes.tooltip}>
<IconButton className={classes.icnBtn} onClick={() => {
_onMoveImageRight()
}}>
<ArrowForwardIcon className={classes.icn} />
</IconButton>
</Tooltip>
<Tooltip title="Move Top" aria-label="top" className=.
{classes.tooltip}>
<IconButton className={classes.icnBtn} onClick={() => {
_onMoveImageTop()
}}>
<ArrowUpwardIcon className={classes.icn} />
</IconButton>
</Tooltip>
<Tooltip title="Move Down" aria-label="down" className={classes.tooltip}>
<IconButton className={classes.icnBtn} onClick={() => {
_onMoveImageBottom()
}}>
<ArrowDownwardIcon className={classes.icn} />
</IconButton>
</Tooltip>
</div>
</div>
</React.Fragment >
);
}
I'm encountering an error when I try to clear the selected tab indicator from my Tabs navigation.
The element contains five individual Tab components in addition to a Button component that reroutes the user to our login screen.
If I click on the Login button, then the console displays the following error: "Material-UI: the value provided 5 to the Tabs component is invalid.
None of the Tabs children have this value.
You can provide one of the following values: 0, 1, 2, 3, 4."
Clicking on the button reroutes to the proper login path ("/login"), but leaves the previous tab showing as selected. However, if I refresh the page, then the selection indicator disappears as it's supposed to.
Any tips on how to remove this error and clear the selected indicator as soon as the Login button is pressed, rather than having to do a refresh?
import React, { useState, useEffect, Fragment } from 'react';
import AppBar from '#material-ui/core/AppBar';
import Toolbar from '#material-ui/core/Toolbar';
import useScrollTrigger from '#material-ui/core/useScrollTrigger';
import { makeStyles } from '#material-ui/styles';
import Tabs from '#material-ui/core/Tabs';
import Tab from '#material-ui/core/Tab';
import Button from '#material-ui/core/Button';
import { Link } from 'react-router-dom';
import Menu from '#material-ui/core/Menu';
import MenuItem from '#material-ui/core/MenuItem';
import useMediaQuery from '#material-ui/core/useMediaQuery';
import { useTheme } from '#material-ui/core/styles';
import SwipeableDrawer from '#material-ui/core/SwipeableDrawer';
import MenuIcon from '#material-ui/icons/Menu';
import IconButton from '#material-ui/core/IconButton';
import List from '#material-ui/core/List';
import ListItem from '#material-ui/core/ListItem';
import ListItemText from '#material-ui/core/ListItemText';
import logo from '../../assets/logo.png';
function ElevationScroll(props) {
const { children, window } = props;
const trigger = useScrollTrigger({
disableHysteresis: true,
threshold: 0,
});
return React.cloneElement(children, {
elevation: trigger ? 4 : 4,
});
}
const useStyles = makeStyles(theme => ({
toolbarMargin: {
...theme.mixins.toolbar,
marginBottom: "1rem",
[theme.breakpoints.down("md")]: {
marginBottom: ".5rem",
transition: "all .2s ease-out"
},
[theme.breakpoints.down("xs")]: {
marginBottom: ".25rem",
transition: "all .2s ease-out"
}
},
logo: {
height: "2.5rem",
margin: "1rem 0 1rem 2rem",
transition: "all .2s ease-out",
[theme.breakpoints.down("md")]: {
height: "2rem",
transition: "all .2s ease-out",
marginLeft: "1.5rem"
},
[theme.breakpoints.down("xs")]: {
marginLeft: "1rem",
height: "1.5rem",
transition: "all .2s ease-out"
}
},
logoContainer: {
padding: 0,
backgroundColor: "transparent"
},
tabContainer: {
marginLeft: "auto"
},
tab: {
...theme.typography.tab,
minWidth: "1rem",
marginLeft: "2rem",
transition: "all .2s ease-out",
"&:hover": {
opacity: 1,
transition: "all .3s ease-out",
borderRadius: "3px",
}
},
button: {
margin: "0 3rem 0 1.5rem",
padding: "0 1.5rem",
height: "2.5rem"
},
styledIndicator: {
backgroundColor: theme.palette.primary.light
},
menu: {
backgroundColor: theme.palette.secondary.dark,
color: theme.palette.background.light,
borderRadius: "0px"
},
menuItem: {
...theme.typography.tab,
opacity: 0.7,
"&:hover": {
opacity: 1,
backgroundColor: theme.palette.common.black
}
},
drawerIcon: {
height: "2rem",
width: "2rem"
},
drawerIconContainer: {
marginLeft: "auto",
"&:hover": {
backgroundColor: "transparent"
}
},
drawer: {
backgroundColor: theme.palette.primary.dark
},
drawerItem: {
...theme.typography.button,
color: theme.palette.background.light,
paddingRight: "3.5rem",
opacity: .85
},
drawerItemLogin: {
...theme.typography.button,
backgroundColor: theme.palette.secondary.dark,
transition: "all .2s ease-out",
"&:hover": {
backgroundColor: theme.palette.common.black
},
"&.Mui-selected": {
backgroundColor: theme.palette.common.black,
"&:hover": {
backgroundColor: theme.palette.common.black
},
}
},
drawerItemSelected: {
opacity: 1
}
}))
export default function Header(props) {
const classes = useStyles();
const theme = useTheme();
const iOS = process.browser && /iPad|iPhone|iPod/.test(navigator.userAgent);
const matches = useMediaQuery(theme.breakpoints.down("md"));
const [openDrawer, setOpenDrawer] = useState(false);
const [value, setValue] = useState(0);
const [anchorEl, setAnchorEl] = useState(null);
const [openMenu, setOpenMenu] = useState(false);
const [selectedIndex, setSelectedIndex] = useState(0);
const handleChange = (e, newValue) => {
setValue(newValue);
}
const handleClick = (e) => {
setAnchorEl(e.currentTarget);
setOpenMenu(true);
}
const handleMenuItemClick = (e, idx) => {
setAnchorEl(null);
setOpenMenu(false);
setSelectedIndex(idx)
}
const handleClose = (e) => {
setAnchorEl(null);
setOpenMenu(false);
}
const menuOptions = [
{
name: "Brands",
link: "/brands",
}, {
name: "Overview",
link: "/overview",
}, {
name: "Use Cases",
link: "/usecases",
}, {
name: "Pricing",
link: "/pricing",
}
]
useEffect(() => {
switch (window.location.pathname) {
case "/":
if (value !== 0) {
setValue(0)
}
break;
case "/artists":
if (value !== 1) {
setValue(1)
}
break;
case "/brands":
if (value !== 2) {
setValue(2)
setSelectedIndex(0)
}
break;
case "/overview":
if (value !== 2) {
setValue(2)
setSelectedIndex(1)
}
break;
case "/usecases":
if (value !== 2) {
setValue(2)
setSelectedIndex(2)
}
break;
case "/pricing":
if (value !== 2) {
setValue(2)
setSelectedIndex(3)
}
break;
case "/about":
if (value !== 3) {
setValue(3)
}
break;
case "/contact":
if (value !== 4) {
setValue(4)
}
break;
case "/login":
if (value !== 5) {
setValue(5);
// setAnchorEl(null)
}
break;
default:
break
}
})
const tabs = (
<Fragment>
<Tabs
value={value}
onChange={handleChange}
className={classes.tabContainer}
classes={{ indicator: classes.styledIndicator }}
indicatorColor="#007EBB"
>
<Tab className={classes.tab} label="Home" component={Link} to={"/"} />
<Tab className={classes.tab} label="Artists" component={Link} to={"/artists"} />
<Tab
className={classes.tab}
label="Brands"
component={Link}
onMouseOver={e => handleClick(e)}
to={"/brands"}
/>
<Tab className={classes.tab} label="About" component={Link} to={"/about"} />
<Tab className={classes.tab} label="Contact" component={Link} to={"/contact"} />
</Tabs>
<Button className={classes.button} variant="contained" color="secondary" component={Link} to={"/login"}>
Login
</Button>
<Menu
anchorEl={anchorEl}
open={openMenu}
onClose={handleClose}
MenuListProps={{ onMouseLeave: handleClose }}
classes={{ paper: classes.menu }}
>
{menuOptions.map((option, idx) => (
<MenuItem
key={option}
component={Link}
to={option.link}
classes={{ root: classes.menuItem }}
onClick={(e) => {
handleMenuItemClick(e, idx);
setValue(2);
handleClose();
}}
selected={idx === selectedIndex && value === 1}
>
{option.name}
</MenuItem>
))}
</Menu>
</Fragment>
)
const drawer = (
<Fragment>
<SwipeableDrawer
disableBackdropTransition={!iOS}
disableDiscovery={iOS}
open={openDrawer}
onClose={() => setOpenDrawer(false)}
onOpen={() => setOpenDrawer(true)}
classes={{ paper: classes.drawer }}
>
<List disablePadding>
<ListItem onClick={() => { setOpenDrawer(false); setValue(0) }} divider button selected={value === 0} component={Link} to="/">
<ListItemText className={value === 0 ? [classes.drawerItem, classes.drawerItemSelected] : classes.drawerItem} disableTypography>Home</ListItemText>
</ListItem>
<ListItem onClick={() => { setOpenDrawer(false); setValue(0) }} divider button selected={value === 1} component={Link} to="/artists">
<ListItemText className={value === 1 ? [classes.drawerItem, classes.drawerItemSelected] : classes.drawerItem} disableTypography>Artists</ListItemText>
</ListItem>
<ListItem onClick={() => { setOpenDrawer(false); setValue(0) }} divider button selected={value === 2} component={Link} to="/brands">
<ListItemText className={value === 2 ? [classes.drawerItem, classes.drawerItemSelected] : classes.drawerItem} disableTypography>Brands</ListItemText>
</ListItem>
{/* <ListItem onClick={() => { setOpenDrawer(false); setValue(0) }} divider button selected={value === 2} component={Link} to="/overview">
<ListItemText className={value === 2 ? [classes.drawerItem, classes.drawerItemSelected] : classes.drawerItem} disableTypography>Overview</ListItemText>
</ListItem>
<ListItem onClick={() => { setOpenDrawer(false); setValue(0) }} divider button selected={value === 2} component={Link} to="/usecases">
<ListItemText className={value === 2 ? [classes.drawerItem, classes.drawerItemSelected] : classes.drawerItem} disableTypography>Use Cases</ListItemText>
</ListItem>
<ListItem onClick={() => { setOpenDrawer(false); setValue(0) }} divider button selected={value === 2} component={Link} to="/pricing">
<ListItemText className={value === 2 ? [classes.drawerItem, classes.drawerItemSelected] : classes.drawerItem} disableTypography>Pricing</ListItemText>
</ListItem> */}
<ListItem onClick={() => { setOpenDrawer(false); setValue(0) }} divider button selected={value === 3} component={Link} to="/about">
<ListItemText className={value === 3 ? [classes.drawerItem, classes.drawerItemSelected] : classes.drawerItem} disableTypography>About</ListItemText>
</ListItem>
<ListItem onClick={() => { setOpenDrawer(false); setValue(0) }} divider button selected={value === 4} component={Link} to="/contact">
<ListItemText className={value === 4 ? [classes.drawerItem, classes.drawerItemSelected] : classes.drawerItem} disableTypography>Contact</ListItemText>
</ListItem>
<ListItem onClick={() => { setOpenDrawer(false); setValue(0) }} className={classes.drawerItemLogin} divider button selected={value === 5} component={Link} to="/login">
<ListItemText
className={value === 5 ? [classes.drawerItem, classes.drawerItemSelected] : classes.drawerItem}
disableTypography
>Login</ListItemText>
</ListItem>
</List>
</SwipeableDrawer>
<IconButton
className={classes.drawerIconContainer}
onClick={() => setOpenDrawer(!openDrawer)}
disableRipple>
<MenuIcon className={classes.drawerIcon} />
</IconButton>
</Fragment >
)
return (
<React.Fragment>
<ElevationScroll>
<AppBar position="fixed" color="primary">
<Toolbar disableGutters={true}>
<Button
component={Link}
to="/"
disableRipple
className={classes.logoContainer}
onClick={() => setValue(0)}
>
<img src={logo} alt="Cuttime Logo" className={classes.logo} />
</Button>
{matches ? drawer : tabs}
</Toolbar>
</AppBar>
</ElevationScroll>
<div className={classes.toolbarMargin} />
</React.Fragment >
);
}
If you don't want any tab selected when on the login page, then you should set the value to false rather than an invalid value (e.g. 5).
From the documentation of the value prop for Tabs (https://material-ui.com/api/tabs/#props):
The value of the currently selected Tab. If you don't want any selected Tab, you can set this property to false.
Below is an example demonstrating this:
import React from "react";
import PropTypes from "prop-types";
import { makeStyles } from "#material-ui/core/styles";
import AppBar from "#material-ui/core/AppBar";
import Tabs from "#material-ui/core/Tabs";
import Tab from "#material-ui/core/Tab";
import Typography from "#material-ui/core/Typography";
import Box from "#material-ui/core/Box";
import Button from "#material-ui/core/Button";
function TabPanel(props) {
const { children, value, index, ...other } = props;
return (
<Typography
component="div"
role="tabpanel"
hidden={value !== index}
id={`simple-tabpanel-${index}`}
aria-labelledby={`simple-tab-${index}`}
{...other}
>
{value === index && <Box p={3}>{children}</Box>}
</Typography>
);
}
TabPanel.propTypes = {
children: PropTypes.node,
index: PropTypes.any.isRequired,
value: PropTypes.any.isRequired
};
function a11yProps(index) {
return {
id: `simple-tab-${index}`,
"aria-controls": `simple-tabpanel-${index}`
};
}
const useStyles = makeStyles(theme => ({
root: {
flexGrow: 1,
backgroundColor: theme.palette.background.paper
}
}));
export default function SimpleTabs() {
const classes = useStyles();
const [value, setValue] = React.useState(false);
const handleChange = (event, newValue) => {
setValue(newValue);
};
return (
<div className={classes.root}>
<AppBar position="static">
<Tabs
value={value}
onChange={handleChange}
aria-label="simple tabs example"
>
<Tab label="Item One" {...a11yProps(0)} />
<Tab label="Item Two" {...a11yProps(1)} />
<Tab label="Item Three" {...a11yProps(2)} />
</Tabs>
</AppBar>
<TabPanel value={value} index={0}>
Item One
</TabPanel>
<TabPanel value={value} index={1}>
Item Two
</TabPanel>
<TabPanel value={value} index={2}>
Item Three
</TabPanel>
{value !== false && (
<Button onClick={() => setValue(false)}>De-select tab</Button>
)}
</div>
);
}
I cannot get the updated state of isAuthenticated when I have Material-UI code.
Is there any way that I can update the state when I have Material-UI code.
import React from "react";
import clsx from "clsx";
import { withStyles } from "#material-ui/core/styles";
import MUILink from "#material-ui/core/Link";
import AppBar from "../UI/components/AppBar";
import Toolbar, { styles as toolbarStyles } from "../UI/components/Toolbar";
import { Link } from "react-router-dom";
import { connect } from 'react-redux';
const styles = theme => ({
title: {
fontSize: 24
},
placeholder: toolbarStyles(theme).root,
toolbar: {
justifyContent: "space-between"
},
left: {
flex: 1
},
leftLinkActive: {
color: theme.palette.common.white
},
right: {
flex: 1,
display: "flex",
justifyContent: "flex-end"
},
rightLink: {
fontSize: 16,
color: theme.palette.common.white,
marginLeft: theme.spacing(3)
},
linkSecondary: {
color: theme.palette.secondary.main
}
});
const Navbar = (props,{isAuthenticated,loading}) => {
const { classes } = props;
const authLinks = (
<div className={classes.right}>
<MUILink
variant="h6"
underline="none"
component={Link} to="/log-out"
className={clsx(classes.rightLink, classes.linkSecondary)}
>
{"Log out"}
</MUILink>
</div>
);
const guestLinks = (
<div className={classes.right}>
<MUILink
color="inherit"
variant="h6"
underline="none"
component={Link} to="/sign-in"
className={classes.rightLink}
>
{"Sign In"}
</MUILink>
<MUILink
variant="h6"
underline="none"
component={Link} to="/sign-up"
className={clsx(classes.rightLink, classes.linkSecondary)}
>
{"Sign Up"}
</MUILink>
</div>
);
return (
<div>
<AppBar position="fixed">
<Toolbar className={classes.toolbar}>
<div className={classes.left} />
<MUILink
variant="h6"
underline="none"
color="inherit"
className={classes.title}
component={Link} to="/"
>
{"buzzer"}
</MUILink>
{!loading && (
<React.Fragment>{isAuthenticated ? authLinks : guestLinks}</React.Fragment>
)}
</Toolbar>
</AppBar>
<div className={classes.placeholder} />
</div>
);
};
const mapStateToProps = state =>({
isAuthenticated:state.auth.isAuthenticated,
loading:state.auth.loading
})
export default connect(mapStateToProps)((withStyles(styles)(Navbar)));
I want that the Navbar to change based on the condition of isAuthenticated. If user is authenticated I want to display only authLink, if the user is not authenticated I want to display the guestLink.
{isAuthenticated,loading} will be injected inside props, and not as a second parameter:
const Navbar = (props) => {
const { classes, isAuthenticated,loading } = props;