I am building a reactjs application -- and I am trying to make this popup component modular -so that I could have the button look like a badge/icon combination -- activate the popup menu on hover instead of clicks.
here is a sandbox -- but I need to create popover menus for each -- at the moment its displacing the buttons.
https://codesandbox.io/s/material-demo-forked-wrn2g?file=/demo.js
Here is the component as it is currently
http://jsfiddle.net/4benm6wo/
import React from 'react';
import Button from '#material-ui/core/Button';
import ClickAwayListener from '#material-ui/core/ClickAwayListener';
import Grow from '#material-ui/core/Grow';
import Paper from '#material-ui/core/Paper';
import Popper from '#material-ui/core/Popper';
import MenuItem from '#material-ui/core/MenuItem';
import MenuList from '#material-ui/core/MenuList';
import Badge from '#material-ui/core/Badge';
import PersonIcon from '#material-ui/icons/Person';
import './PopOverMenu.scss';
export default function MenuListComposition() {
const [open, setOpen] = React.useState(false);
const anchorRef = React.useRef(null);
const handleToggle = () => {
setOpen((prevOpen) => !prevOpen);
};
const handleClose = (event) => {
if (anchorRef.current && anchorRef.current.contains(event.target)) {
return;
}
setOpen(false);
};
function handleListKeyDown(event) {
if (event.key === 'Tab') {
event.preventDefault();
setOpen(false);
}
}
// return focus to the button when we transitioned from !open -> open
const prevOpen = React.useRef(open);
React.useEffect(() => {
if (prevOpen.current === true && open === false) {
anchorRef.current.focus();
}
prevOpen.current = open;
}, [open]);
return (
<div className="popover-menu">
<div>
<Button
ref={anchorRef}
aria-controls={open ? 'menu-list-grow' : undefined}
aria-haspopup="true"
onClick={handleToggle}
>
<Badge badgeContent={11} color="primary">
<PersonIcon />
</Badge>
</Button>
<Popper open={open} anchorEl={anchorRef.current} role={undefined} transition disablePortal>
{({ TransitionProps, placement }) => (
<Grow
{...TransitionProps}
style={{ transformOrigin: placement === 'bottom' ? 'center top' : 'center bottom' }}
>
<Paper>
<ClickAwayListener onClickAway={handleClose}>
<MenuList autoFocusItem={open} id="menu-list-grow" onKeyDown={handleListKeyDown}>
<MenuItem onClick={handleClose}>Profile</MenuItem>
<MenuItem onClick={handleClose}>My account</MenuItem>
<MenuItem onClick={handleClose}>Logout</MenuItem>
</MenuList>
</ClickAwayListener>
</Paper>
</Grow>
)}
</Popper>
</div>
</div>
);
}
but I want to create a popperMenu component -- where I push the icon, badge count into the poppup -- so I need help implement it with props and states.
this is my current attempt
http://jsfiddle.net/4benm6wo/1/
class MenuListComposition extends Component {
constructor(props, context) {
super(props, context);
this.state = { open: false };
}
render() {
return (
<div className="popover-menu">
<div>
<Button
ref={anchorRef}
aria-controls={open ? 'menu-list-grow' : undefined}
aria-haspopup="true"
onClick={handleToggle}
>
<Badge badgeContent={11} color="primary">
<PersonIcon />
</Badge>
</Button>
<Popper open={open} anchorEl={anchorRef.current} role={undefined} transition disablePortal>
{({ TransitionProps, placement }) => (
<Grow
{...TransitionProps}
style={{ transformOrigin: placement === 'bottom' ? 'center top' : 'center bottom' }}
>
<Paper>
<ClickAwayListener onClickAway={handleClose}>
<MenuList autoFocusItem={open} id="menu-list-grow" onKeyDown={handleListKeyDown}>
<MenuItem onClick={handleClose}>Profile</MenuItem>
<MenuItem onClick={handleClose}>My account</MenuItem>
<MenuItem onClick={handleClose}>Logout</MenuItem>
</MenuList>
</ClickAwayListener>
</Paper>
</Grow>
)}
</Popper>
</div>
</div>
)
}
}
export default MenuListComposition;
so I would create a button/badge popup kind of like this from the shell
<MenuListComposition badgeCount={10} icon={<PersonIcon />} menu={[{ "label": "Profile", "link": "user/1" }, { "label": "Logout", "link": "logout" }]} />
latest code
LoginButton.js
http://jsfiddle.net/z3L89x2e/
import React, { Component } from 'react'
import { withRouter } from 'react-router-dom';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import Button from '#material-ui/core/Button';
import PopOverMenu from '../_SharedGlobalComponents/PopOverMenu/PopOverMenu';
import MailIcon from '#material-ui/icons/Mail';
import NotificationsIcon from '#material-ui/icons/Notifications';
import PersonIcon from '#material-ui/icons/Person';
class LoggedInButtons extends Component {
/*
constructor(props, context) {
super(props, context);
}
*/
render() {
return (
<div className="login-badges">
<PopOverMenu
icon={<NotificationsIcon />}
badgecount="3"
menu={[
{ "label": "xxx", "value": "/" },
{ "label": "xxxx", "value": "/" },
{ "label": "xxxxx", "value": "/" },
]}
/>
<PopOverMenu
icon={<MailIcon />}
badgecount="5"
menu={[
{ "label": "xxx", "value": "/" },
{ "label": "xxxx", "value": "/" },
{ "label": "xxxxx", "value": "/" },
]}
/>
<PopOverMenu
icon={<PersonIcon />}
badgecount="2"
menu={[
{ "label": "Profile", "value": "/profile" },
{ "label": "My account", "value": "/my-account" },
{ "label": "Logout", "value": "/logout" },
]}
/>
<Button
variant="text"
color="default"
startIcon={<PersonIcon />}
href="/user/view/2"
>
User 2
</Button>
<Button variant="contained" color="secondary" href="/logout">
log out
</Button>
</div>
)
}
}
function mapStateToProps(state) {
return {
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({ }, dispatch);
}
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(LoggedInButtons))
PopOverMenu.js
http://jsfiddle.net/z3L89x2e/2/
import React, { Component } from 'react';
import Button from '#material-ui/core/Button';
import ClickAwayListener from '#material-ui/core/ClickAwayListener';
import Grow from '#material-ui/core/Grow';
import Paper from '#material-ui/core/Paper';
import Popper from '#material-ui/core/Popper';
import MenuItem from '#material-ui/core/MenuItem';
import MenuList from '#material-ui/core/MenuList';
import Badge from '#material-ui/core/Badge';
//import './PopOverMenu.scss';
class PopOverMenu extends Component {
constructor(props, context) {
super(props, context);
this.state = { open: false };
this.anchorRef = React.createRef(null);
}
handleToggle = () => {
this.setState({open: !this.state.open});
};
handleClose = () => {
this.setState({open: false});
};
handleListKeyDown = (event) => {
if (event.key === 'Tab') {
event.preventDefault();
this.setState({open: false});
}
}
showMenuItems = () => (
this.props.menu.map((item, i) => (
<MenuItem onClick={this.handleClose}>{item.label}</MenuItem>
))
)
render() {
return (
<div style={{display:'inline', float:'left', marginLeft:'20px', marginTop:'10px'}} className="popover-menu">
<Button
ref={this.anchorRef}
aria-controls={this.state.open ? 'menu-list-grow' : null}
aria-haspopup="true"
onClick={this.handleToggle}
>
<Badge badgeContent={this.props.badgecount} color="primary">
{this.props.icon}
</Badge>
</Button>
<Popper style={{position: 'relative'}} open={this.state.open} role={undefined} transition disablePortal>
{({ TransitionProps, placement }) => (
<Grow
{...TransitionProps}
style={{ transformOrigin: placement === 'bottom' ? 'center top' : 'center bottom' }}
>
<Paper>
<ClickAwayListener onClickAway={this.handleClose}>
<MenuList autoFocusItem={this.state.open} id="menu-list-grow" onKeyDown={this.handleListKeyDown}>
{this.showMenuItems()}
</MenuList>
</ClickAwayListener>
</Paper>
</Grow>
)}
</Popper>
</div>
);
}
}
export default PopOverMenu;
this is the latest attempt - it nearly worked - but then I got errors in the ancor el
I am using material ui and the icon/badge modules. I started to get an error on hovering over the menu.
I've tried to follow something like this - of putting the ancor el into the state - but its not working.
How can I convert popover MATERIAL-UI functional component to class based component?
import React, { Component } from 'react';
import { NavLink } from 'react-router-dom';
import Button from '#material-ui/core/Button';
import ClickAwayListener from '#material-ui/core/ClickAwayListener';
import Grow from '#material-ui/core/Grow';
import Paper from '#material-ui/core/Paper';
import Popper from '#material-ui/core/Popper';
import MenuItem from '#material-ui/core/MenuItem';
import MenuList from '#material-ui/core/MenuList';
import Badge from '#material-ui/core/Badge';
import './PopOverMenu.scss';
class PopOverMenu extends Component {
constructor(props, context) {
super(props, context);
// this.state = { open: false};
// this.anchorRef = React.createRef(null);
this.state = { anchorEl: null, open: false };
}
handleToggle = (event) => {
this.setState({open: !this.state.open});
// this.state.ancherEl ? this.setState({ anchorEl: null }) : this.setState({ anchorEl: event.currentTarget });
};
handleOpen = (event) => {
this.setState({open: true});
console.log("event.currentTarget", event.currentTarget);
this.state.ancherEl ? this.setState({ anchorEl: null }) : this.setState({ anchorEl: event.currentTarget });
};
handleClose = () => {
this.setState({open: false});
//this.setState({ anchorEl: null })
};
handleListKeyDown = (event) => {
if (event.key === 'Tab') {
event.preventDefault();
this.setState({open: false});
}
}
showMenuItems = () => (
this.props.menu.map((item, i) => (
<MenuItem key={i} onClick={this.handleClose}>
<NavLink to={item.value}>{item.label}</NavLink>
</MenuItem>
))
)
render() {
//console.log("this.anchorRef", this.anchorRef)
return (
<div className="popover-menu">
<Button
//ref={this.anchorRef}
aria-controls={this.state.open ? 'menu-list-grow' : null}
aria-haspopup="true"
onClick={this.handleToggle}
onMouseOver={this.handleOpen}
onMouseLeave={this.handleClose}
>
<Badge badgeContent={this.props.badgecount} color="primary">
{this.props.icon}
</Badge>
</Button>
<Popper
className="popper-list"
//anchorEl={this.anchorRef}
open={this.state.open}
anchorEl={this.state.anchorEl}
//role={undefined}
transition
disablePortal
onMouseOver={this.handleOpen}
onMouseLeave={this.handleClose}
>
{({ TransitionProps, placement }) => (
<Grow
{...TransitionProps}
style={{ transformOrigin: placement === 'bottom' ? 'center top' : 'center bottom' }}
>
<Paper>
<ClickAwayListener onClickAway={this.handleClose}>
<MenuList autoFocusItem={this.state.open} id="menu-list-grow" onKeyDown={this.handleListKeyDown}>
{/*this.showMenuItems()*/}
</MenuList>
</ClickAwayListener>
</Paper>
</Grow>
)}
</Popper>
</div>
);
}
}
export default PopOverMenu;
.popover-menu{
width: 40px;
display: inline;
float: left;
//border: 1px solid red;
//background: blue;
padding: 5px;
.popper-list{
width: 160px;
position: relative!important;
//border: 1px solid blue;
}
}
Here is a working CodeSandbox
You are using the wrong ref.
You need to assign the ref to the Popper on click (see line 89).
To be able to do this, you need to set the ref using setState and not createRef.
for hover activation: You need to toggle it using onMouseEnter and onMouseLeave on parent div
Add onClick on button to toggle the popper
Related
I'm hoping to set up protected routes for my webapp built with React.js. I have an array 'menuOptions' that I have been looping through to add options to my navbar. However, I've now been running in to issues as I would like to add protected routes.
So for instance, I would like to have Home, Login and Register when the user is logged out, and Dashboard and Logout as the options for when the user is logged in. I'd really appreciate any help. Ideally, I'd somehow add the '!user' to the menuOptions array somehow.
My code is below (as you can see menuOptions does not do anything for the above at the moment):
import React, { useState, useContext } from "react";
import { Link, NavLink, useNavigate } from "react-router-dom";
import { useAuthState } from "react-firebase-hooks/auth";
import { auth, logout } from "../../firebase";
import { makeStyles } from "#material-ui/core/styles";
import AppBar from "#material-ui/core/AppBar";
import Toolbar from "#material-ui/core/Toolbar";
import Typography from "#material-ui/core/Typography";
import IconButton from "#material-ui/core/IconButton";
import MenuIcon from "#material-ui/icons/Menu";
import MenuItem from "#material-ui/core/MenuItem";
import Menu from "#material-ui/core/Menu";
import { useTheme } from "#material-ui/core/styles";
import useMediaQuery from "#material-ui/core/useMediaQuery";
const useStyles = makeStyles((theme) => ({
title: {
flexGrow: 1,
},
appbar: {
// background: 'none',
},
inactiveLink: {
color: "white",
padding: theme.spacing(1),
fontSize: "1.5rem",
},
activeLink: {
color: "black",
padding: theme.spacing(1),
fontSize: "1.5rem",
background: "#bfbfbf",
},
}));
function Navigation() {
const classes = useStyles();
const [anchorEl, setAnchorEl] = useState(null);
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down("md"));
const open = Boolean(anchorEl);
const menuOptions = [
{ label: "Home", path: "/", },
{ label: "Register", path: "/register" },
{ label: "Dashboard", path: "/dashboard" },
{ label: "Logout", path: "/logout" },
];
const handleMenuSelect = (pageURL) => {
navigate(pageURL);
};
const handleMenu = (event) => {
setAnchorEl(event.currentTarget);
};
const [user] = useAuthState(auth);
const navigate = useNavigate();
return (
<>
<AppBar
className={classes.appbar}
position="fixed"
elevation={0}
color="primary"
>
<Toolbar>
<Typography variant="h4" className={classes.title}>
The Football App
</Typography>
<Typography variant="h6" className={classes.title}>
All you ever wanted to know about Football!
</Typography>
{isMobile ? (
<>
<IconButton
aria-label="menu"
aria-controls="menu-appbar"
aria-haspopup="true"
onClick={handleMenu}
color="inherit"
>
<MenuIcon />
</IconButton>
<Menu
id="menu-appbar"
anchorEl={anchorEl}
anchorOrigin={{
vertical: "top",
horizontal: "right",
}}
keepMounted
transformOrigin={{
vertical: "top",
horizontal: "right",
}}
open={open}
onClose={() => setAnchorEl(null)}
>
{menuOptions.map((opt) => (
<MenuItem
key={opt.label}
onClick={() => handleMenuSelect(opt.path)}
>
{opt.label}
</MenuItem>
))}
</Menu>
</>
) : (
<>
<nav>
<div>
{!user ? (
<NavLink
to="/"
className={({ isActive }) =>
isActive
}
>
Home
</NavLink>
) : (
<NavLink
to="/dashboard"
className={({ isActive }) =>
isActive
}
>
Dashboard
</NavLink>
)}
</div>
{!user ? (
<div>
<Link to="/login">
<a>Login</a>
</Link>
<Link to="/register">
<a>Register</a>
</Link>
</div>
) : (
<div className="navbar-end">
<Link to="/logout">
<a>Log Out</a>
</Link>
</div>
)}
</nav>
</>
)}
</Toolbar>
</AppBar>
</>
);
}
export default Navigation;
You could update your menuOptions object to include a protected key:
const menuOptions = [
{ label: "Home", path: "/", protected: false },
{ label: "Register", path: "/register", protected: false },
{ label: "Dashboard", path: "/dashboard", protected: false },
{ label: "Logout", path: "/logout", protected: true },
];
and update the logic where your render your menu options to something like
{menuOptions.map((opt) => {
if (opt.protected && !user) return;
return (
<MenuItem
key={opt.label}
onClick={() => handleMenuSelect(opt.path)}
>
{opt.label}
</MenuItem>
);
}
)}
to only show certain options if the user exists
{menuOptions.filter((opt) => (user ? opt.protected : !opt.protected)).map((opt) => (
<MenuItem
key={opt.label}
onClick={() => handleMenuSelect(opt.path)}
>
{opt.label}
</MenuItem>
)
)}
I have a modal window / Cookies dialog that I want to be launched when I open my application, without having to press any buttons, before showing my Dashboard.
this is my dashboard:
import { getDocument } from '../actions/documentAction';
//
import Grid from '#mui/material/Grid';
import { ThemeProvider } from '#mui/material'
//
import theme from '../styles/theme'
import '../styles/styles.css'
//
import HeaderBar from './header-bar/HeaderBar'
import BodyGrid from './body-grid/BodyGrid';
import BottomBar from './botton-bar/BottonBar';
// -----------------------------------------------------------
export const Dashboard = () => {
const dispatch = useDispatch();
dispatch( getDocument( 'EIT5850', {} ) );
//dispatch( getPrintCenter( 'EIT5850' ) );
return (
<>
<ThemeProvider theme={theme}>
<div>
<HeaderBar/>
<Grid container xs={12}>
<BodyGrid/>
</Grid>
<Grid container xs={12} pt={4}>
<BottomBar/>
</Grid>
</div>
</ThemeProvider>
</>
)
}
This is my Cookies modal:
import * as React from 'react';
import Backdrop from '#mui/material/Backdrop';
import Box from '#mui/material/Box';
import Modal from '#mui/material/Modal';
import Fade from '#mui/material/Fade';
import Button from '#mui/material/Button';
import Typography from '#mui/material/Typography';
import {Stack, ThemeProvider } from '#mui/material'
import Tab from '#mui/material/Tab';
import TabContext from '#mui/lab/TabContext';
import TabList from '#mui/lab/TabList';
import TabPanel from '#mui/lab/TabPanel';
import { FormControlLabel, IconButton } from '#mui/material';
import CloseIcon from '#mui/icons-material/Close';
import PropTypes from 'prop-types';
import { styled } from '#mui/material/styles';
import Dialog from '#mui/material/Dialog';
import DialogTitle from '#mui/material/DialogTitle';
import DialogContent from '#mui/material/DialogContent';
import DialogActions from '#mui/material/DialogActions';
import theme from '../../styles/theme'
import '../../styles/styles.css'
const style = {
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: 1800,
bgcolor: 'background.paper',
border: '2px solid #000',
boxShadow: 24,
p: 4,
};
const BootstrapDialog = styled(Dialog)(({ theme }) => ({
'& .MuiDialogContent-root': {
padding: theme.spacing(2),
},
'& .MuiDialogActions-root': {
padding: theme.spacing(1),
},
}));
const BootstrapDialogTitle = (props) => {
const { children, onClose, ...other } = props;
return (
<DialogTitle sx={{ m: 0, p: 2 }} {...other}>
{children}
{onClose ? (
<IconButton
aria-label="close"
onClick={onClose}
sx={{
position: 'absolute',
right: 8,
top: 8,
color: (theme) => theme.palette.grey[500],
}}
>
<CloseIcon />
</IconButton>
) : null}
</DialogTitle>
);
};
BootstrapDialogTitle.propTypes = {
children: PropTypes.node,
onClose: PropTypes.func.isRequired,
};
export default function ModalNonDocs() {
const handleOpen = () => setOpen(true);
const [open, setOpen] = React.useState(false);
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
const handleNotClose = (event, reason) => {
if (reason && reason == "backdropClick")
return;
}
const [value, setValue] = React.useState('1');
const handleChange = (event, newValue) => {
setValue(newValue);
};
return (
<ThemeProvider theme={theme}>
<div>
<Fade in={open}>
<Box>
<BootstrapDialog
onClose={handleNotClose}
aria-labelledby="customized-dialog-title"
open={open}
closeAfterTransition
BackdropComponent={Backdrop}
BackdropProps={{
timeout: 500,
}}
>
<BootstrapDialogTitle id="customized-dialog-title" onClose={handleClose}>
</BootstrapDialogTitle>
<TabContext value={value}>
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
<TabList onChange={handleChange} aria-label="lab API tabs example">
<Tab
label="Consentimiento"
value="1" />
<Tab
label="PolĂtica de Cookies"
value="2" />
<Tab
label="Acerca de las Cookies"
value="3" />
</TabList>
</Box>
<TabPanel value="1">
<p>Opcion 1</p>
</TabPanel>
<TabPanel value="2">
<p>Opcion 2</p>
</TabPanel>
<TabPanel value="3">
<p>Opcion 3</p>
</TabPanel>
</TabContext>
<Button
onClick={handleClose}
variant="contained"
color="secondary"
style={
{ borderRadius: 0 ,
fontFamily: 'Source Sans Pro'}
}
>
Aceptar
</Button>
</BootstrapDialog>
</Box>
</Fade>
</div>
</ThemeProvider>
);
}
and this is where I call my dashboard to launch it
import { Routes, Route, BrowserRouter } from 'react-router-dom';
import { Dashboard } from '../components/Dashboard';
import { PrivateRoute } from './PrivateRoute';
export const AppRouter = () => {
return (
<BrowserRouter>
<Routes>
<Route path="/*" element={
<PrivateRoute>
<Dashboard />
</PrivateRoute>
} />
</Routes>
</BrowserRouter>
)
}
Until now I launched my cookie modal through an informative button, but now I need it to be shown automatically when the application starts.
I can't find a way to do it without a button
I am using MaterialUi Snackbar with Slide transition.
Expected Behaviour: Snackbar should slide in the provided direction and retreat on close.
This code is working.
Here's the code:
import React, { useEffect, useState } from "react";
import Snackbar from "#mui/material/Snackbar";
import Slide from "#mui/material/Slide";
import Alert from "#mui/material/Alert";
const Transition = (newprops) => {
console.log("CustomMUISnackBar Transition props: ", newprops);
return <Slide {...newprops} direction="up" />;
};
function CustomMUISnackBar(props) {
const [open, setOpen] = useState(false);
useEffect(() => {
setOpen(props.open);
}, [props.open]);
return (
<Snackbar
autoHideDuration={1000}
TransitionComponent={Transition}
anchorOrigin={{ vertical: "top", horizontal: "center" }}
open={open}
onClose={() => {
setOpen(false);
props.onClose();
}}
key="Slide"
>
<Alert
onClose={() => {
setOpen(false);
props.onClose();
}}
severity="success"
sx={{ width: "100%" }}
>
<i style={{ color: "blue" }}>{props.updatedUserName}</i>'s details
updated!
</Alert>
</Snackbar>
);
}
export default CustomMUISnackBar;
But if I place Transition function inside the functional component's body then only the first part of the Slide works as in it shows the snackbar opening with sliding but disappears without sliding.
function CustomMUISnackBar(props) {
const [open, setOpen] = useState(false);
const Transition = (newprops) => {
console.log("CustomMUISnackBar Transition props: ", newprops);
return <Slide {...newprops} direction="up" />;
};
return ();
}
export default CustomMUISnackBar;
It only happens when I use the Snackbar in a separate Component.js and then use it somewhere. If I use it directly it works whether I place the Transition function inside the component or outside it.
Below code works for both buttons:
import React, { useState } from "react";
import Snackbar from "#mui/material/Snackbar";
import Slide from "#mui/material/Slide";
import Alert from "#mui/material/Alert";
const OutsideTransition = (tprops) => {
console.log("Outside Transition props: ", tprops);
return <Slide {...tprops} direction="up" />;
};
function CustomMUISnackBar(props) {
const [state, setState] = useState({
open: false,
transition: undefined
});
const InsideTransition = (tprops) => {
console.log("Outside Transition props: ", tprops);
return <Slide {...tprops} direction="up" />;
};
return (
<div>
<Snackbar
autoHideDuration={1000}
TransitionComponent={state.transition}
anchorOrigin={{ vertical: "top", horizontal: "center" }}
open={state.open}
onClose={() => setState({ ...state, open: false })}
key={state.transition?.name}
>
<Alert
onClose={() => setState({ ...state, open: false })}
severity="success"
>
<i style={{ color: "blue" }}>"Update Success!"</i>'s details updated!
</Alert>
</Snackbar>
<button
onClick={() => setState({ open: true, transition: OutsideTransition })}
>
SanckBar OutsideTransition
</button>
<button
onClick={() => setState({ open: true, transition: InsideTransition })}
>
SanckBar InsideTransition
</button>
</div>
);
}
export default CustomMUISnackBar;
If I use class Component in a separate component.js file, then also it works as expected.
This code works:
import React, { useEffect, useState } from "react";
import Snackbar from "#mui/material/Snackbar";
import Slide from "#mui/material/Slide";
import Alert from "#mui/material/Alert";
const Transition = (newprops) => {
console.log("CustomMUISnackBar Transition props: ", newprops);
return <Slide {...newprops} direction="up" />;
};
class CustomMUISnackBarClass extends React.Component {
constructor(props) {
super(props);
this.state = { open: props.open };
}
TransitionMethod = (newprops) => {
console.log("CustomMUISnackBar Transition props: ", newprops);
return <Slide {...newprops} direction="up" />;
};
render() {
return (
<Snackbar
autoHideDuration={1000}
TransitionComponent={this.TransitionMethod}
anchorOrigin={{ vertical: "top", horizontal: "center" }}
open={this.state.open}
onClose={() => {
this.setState({ open: false });
this.props.onClose();
}}
key="Slide"
>
<Alert
onClose={() => {
this.setState({ open: false });
this.props.onClose();
}}
severity="success"
sx={{ width: "100%" }}
>
<i style={{ color: "blue" }}>{this.props.updatedUserName}</i>'s
details updated!
</Alert>
</Snackbar>
);
}
}
export default CustomMUISnackBarClass;
Above code works even if TransitionComponent={this.TransitionMethod} is replaced with TransitionComponent={Transition}.
So, my question is how the functionality is getting differed in functional and class component. What is changing if a function is declared inside a functional component or outside as compared to behaviour when a functional component is defined in a component.js file?
Here's the code at codesandbox: https://codesandbox.io/s/transitionsnackbar-eillp. Wait for few seconds before clicking on other buttons.
I am getting this error on my React material UI project
By looking at the error I guess it comes somewhere inside the Drawer.js component.
This is my full Drawer.js component
import React, { Fragment, useState } from "react";
import clsx from "clsx";
import { makeStyles, useTheme } from "#material-ui/core/styles";
import Drawer from "#material-ui/core/Drawer";
import { List, Collapse } from "#material-ui/core";
import Divider from "#material-ui/core/Divider";
import ListItem from "#material-ui/core/ListItem";
import IconButton from "#material-ui/core/IconButton";
import ListItemText from "#material-ui/core/ListItemText";
import { toggleDrawer } from "../../store/actions/authActions";
import ExpandLess from "#material-ui/icons/ExpandLess";
import ExpandMore from "#material-ui/icons/ExpandMore";
import ChevronLeftIcon from "#material-ui/icons/ChevronLeft";
import ChevronRightIcon from "#material-ui/icons/ChevronRight";
// import ClickAwayListener from '#material-ui/core/ClickAwayListener'
import { useHistory } from 'react-router-dom';
import { connect } from "react-redux";
import { withRouter } from "react-router";
const useStyles = makeStyles({
list: {
width: 250,
},
fullList: {
width: "auto",
},
});
function TemporaryDrawer(props) {
const classes = useStyles();
const theme = useTheme();
// const [open, setOpen] = React.useState(false);
const [openIndex, setOpenIndex] = useState(0);
let history = useHistory();
// const handleDrawerOpen = () => {
// setOpen(true);
// };
const handleDrawerClose = () => {
props.toggleDrawer();
};
const handleClick = (index) => {
if (openIndex === index) {
setOpenIndex(-1);
} else {
setOpenIndex(index);
}
};
const onToggle = () => (event) => {
if (
event.type === "keydown" &&
(event.key === "Tab" || event.key === "Shift")
) {
return;
}
props.toggleDrawer();
};
const onRoute = (path) => {
// props.history.push(path);
history.push(path);
props.toggleDrawer();
};
const list = (anchor) => (
<div
className={clsx(classes.list, {
[classes.fullList]: anchor === "top" || anchor === "bottom",
})}
role="presentation"
>
<div className={classes.drawerHeader}>
<IconButton onClick={handleDrawerClose}>
{theme.direction === "ltr" ? (
<ChevronLeftIcon />
) : (
<ChevronRightIcon />
)}
</IconButton>
</div>
<Divider />
<List>
{props.permissions.map((route, index) => (
<Fragment key={index}>
<ListItem button onClick={(e) => handleClick(index)}>
<ListItemText primary={route.name} />
{index === openIndex ? <ExpandLess /> : <ExpandMore />}
</ListItem>
{route.children.length && (
<Collapse
in={openIndex === index ? true : false}
timeout="auto"
unmountOnExit
>
<List component="div" disablePadding>
{route.children.map((child, idx) => (
<ListItem
button
className={classes.nested}
key={idx}
onClick={() => onRoute(child.path)}
>
<ListItemText primary={child.name} />
</ListItem>
))}
</List>
<Divider />
</Collapse>
)}
</Fragment>
))}
</List>
{/* <Divider /> */}
</div>
);
return (
<div>
{props.token && (
<Drawer
anchor="left"
open={props.isDrawerOpen}
onClose={onToggle("left", false)}
variant="persistent"
>
{list("left")}
</Drawer>
)}
</div>
);
}
const mapStateToProps = (state) => {
return {
isDrawerOpen: state.auth.isDrawerOpen,
token: state.auth.token,
permissions: state.auth.routes,
};
};
export default withRouter(
connect(mapStateToProps, { toggleDrawer })(TemporaryDrawer)
);
I go through this error and some say this is a problem in MUI library and no way to fix this. But I believe there must be a workaround for this. This causes serious problems for UI.
Where this error comes from and what can I do to fix this?
Any help!
Thanks in advance. =)
I use material UI 4.11.0
and react 16
You only need to create a new childComponent with react.forwardRef passing respective props and refs:
const ChildComponent = React.forwardRef((props, ref) =>
<div ref={ref}>
<yourChildcomponent {...props} />
</div>
);
In your render change the name of the original Child for the new:
<ChildComponent... />
I have reading material-ui documents and I have found this syntax : anchorEl: null | HTMLElement
I don't have any clue what is it for.
Any ideas?
import React from 'react';
import PropTypes from 'prop-types';
import { createStyles, withStyles, WithStyles } from '#material-ui/core/styles';
import AppBar from '#material-ui/core/AppBar';
import Toolbar from '#material-ui/core/Toolbar';
import Typography from '#material-ui/core/Typography';
import IconButton from '#material-ui/core/IconButton';
import MenuIcon from '#material-ui/icons/Menu';
import AccountCircle from '#material-ui/icons/AccountCircle';
import Switch from '#material-ui/core/Switch';
import FormControlLabel from '#material-ui/core/FormControlLabel';
import FormGroup from '#material-ui/core/FormGroup';
import MenuItem from '#material-ui/core/MenuItem';
import Menu from '#material-ui/core/Menu';
const styles = createStyles({
root: {
flexGrow: 1,
},
grow: {
flexGrow: 1,
},
menuButton: {
marginLeft: -12,
marginRight: 20,
},
});
export interface Props extends WithStyles<typeof styles> {}
export interface State {
auth: boolean;
anchorEl: null | HTMLElement;
}
class MenuAppBar extends React.Component<Props, State> {
state: State = {
auth: true,
anchorEl: null,
};
handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
this.setState({ auth: event.target.checked });
};
handleMenu = (event: React.MouseEvent<HTMLElement>) => {
this.setState({ anchorEl: event.currentTarget });
};
handleClose = () => {
this.setState({ anchorEl: null });
};
render() {
const { classes } = this.props;
const { auth, anchorEl } = this.state;
const open = Boolean(anchorEl);
return (
<div className={classes.root}>
<FormGroup>
<FormControlLabel
control={
<Switch checked={auth} onChange={this.handleChange} aria-label="LoginSwitch" />
}
label={auth ? 'Logout' : 'Login'}
/>
</FormGroup>
<AppBar position="static">
<Toolbar>
<IconButton className={classes.menuButton} color="inherit" aria-label="Menu">
<MenuIcon />
</IconButton>
<Typography variant="h6" color="inherit" className={classes.grow}>
Photos
</Typography>
{auth && (
<div>
<IconButton
aria-owns={open ? 'menu-appbar' : undefined}
aria-haspopup="true"
onClick={this.handleMenu}
color="inherit"
>
<AccountCircle />
</IconButton>
<Menu
id="menu-appbar"
anchorEl={anchorEl}
anchorOrigin={{
vertical: 'top',
horizontal: 'right',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'right',
}}
open={open}
onClose={this.handleClose}
>
<MenuItem onClick={this.handleClose}>Profile</MenuItem>
<MenuItem onClick={this.handleClose}>My account</MenuItem>
</Menu>
</div>
)}
</Toolbar>
</AppBar>
</div>
);
}
}
(MenuAppBar as React.ComponentClass<Props>).propTypes = {
classes: PropTypes.object.isRequired,
} as any;
export default withStyles(styles)(MenuAppBar);