How do i implement a dropdown header in Material-ui in react? - javascript

My main problem is that it's only rendering the last Menu Dropdown but i need different Menus (and you can see the text behind it faintly appearing). Am unsure how to pass the correct props / state to enable this
import React from 'react';
import {Button, Menu, MenuItem} from "#material-ui/core";
function Header(){
const [anchorEl, setAnchorEl] = React.useState(null);
const handleClick = (event) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
return (
<div>
<Button aria-controls="sessions-menu" aria-haspopup="true" onClick={handleClick}>
Sessions
</Button>
<Button aria-controls="store-menu" aria-haspopup="true" onClick={handleClick}>
Store
</Button>
<Button aria-controls= "about-menu" aria-haspopup="true" onClick={About} href="/about">
About
</Button>
<Button aria-controls="account-menu" aria-haspopup="true" onClick={handleClick}>
Account
</Button>
<Menu
id="sessions-menu"
anchorEl={anchorEl}
getContentAnchorEl={null}
keepMounted
open={Boolean(anchorEl)}
onClose={handleClose}
>
<MenuItem onClick={Book} href="/sessions/book">Book a Session</MenuItem>
<MenuItem onClick={Host} href="/sessions/host">[S] Host a session</MenuItem>
</Menu>
<Menu
id="store-menu"
anchorEl={anchorEl}
getContentAnchorEl={null}
keepMounted
open={Boolean(anchorEl)}
onClose={handleClose}
>
<MenuItem onClick={Purchase}>Purchase</MenuItem>
<MenuItem onClick={Sell}>[S] Sell</MenuItem>
</Menu>
<Menu
id="about-menu"
anchorEl={anchorEl}
keepMounted
open={Boolean(anchorEl)}
onClose={handleClose}
></Menu>
<Menu
id="account-menu"
anchorEl={anchorEl}
keepMounted
open={Boolean(anchorEl)}
onClose={handleClose}
>
<MenuItem onClick={Lessons}>My Lessons</MenuItem>
<MenuItem onClick={Items}>My Purchases</MenuItem>
</Menu>
</div>
);
}
export default Header;
Any help or advice for a simpler way to achieve what i want would be swell

As given in documentation anchorEl - It's used to set the position of the menu. In your code you used same anchorEl for all menu and as a result it's only rendering the last Menu Dropdown.
solution is to have anchorEl separate to each menu. for that you need to create scoped MenuButton component for each button with its menu.
You can have separate component for each button with its menu (duplication) however it better have an array of menus and render it with single component(reusability).
please check running code here https://codesandbox.io/s/header-menu-dropdown-e9e7p
I will put Header and MenuButton code here if link not work.
Header.js
import React from "react";
import MenuButton from "./MenuButton";
const Header = () => {
//added only two menu to demonstrate you can add more as per your requirement
const menu = [
{
name: "Sessions",
menuItems: [
{
name: "Book a Session",
onClick: () => {},
href:""
},
{
name: "[S] Host a session",
onClick: () => {},
href:""
}
]
},
{
name: "Store",
menuItems: [
{
name: "Purchase",
onClick: () => {}
},
{
name: "Sell",
onClick: () => {}
}
]
}
];
return menu.map((item, index) => <MenuButton key={index} menu={item} />);
};
export default Header;
MenuButton.js (button with its menu)
import React from "react";
import { Button, Menu, MenuItem } from "#material-ui/core";
const MenuButton = ({ menu }) => {
const [anchorEl, setAnchorEl] = React.useState(null);
const handleClick = (event) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
return (
<>
<Button
aria-controls={`${menu.name}-menu`}
aria-haspopup="true"
onClick={handleClick}
>
{menu.name}
</Button>
<Menu
id={`${menu.name}-menu`}
anchorEl={anchorEl}
getContentAnchorEl={null}
keepMounted
open={Boolean(anchorEl)}
onClose={handleClose}
>
{menu.menuItems.map((item) => (
<MenuItem onClick={item.onClick} href={item.href}>
{item.name}
</MenuItem>
))}
</Menu>
</>
);
};
export default MenuButton;

Related

How to place protected routes within an array in React.js?

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>
)
)}

React JS pass click event to another component

I have Avatar and Menu components inside the Navbar component. I want to trigger the function from Menu component by clicking on Avatar component. My question is how to pass a clickEvent from avatar to Menu component.
<AppBar position="sticky" className={classes.navBarStyle}>
<Toolbar>
<Avatar alt="" className={classes.userAvatar} src="./avatar.jpg"/>
<DropDownMenu/>
</Toolbar>
</AppBar>
function DropDownMenu() {
const [anchorEl, setAnchorEl] = React.useState(null);
const open = Boolean(anchorEl);
const handleClick = (event) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
return (
<div>
<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>
</div>
);
}
You don't need to pass the click event around, you just need to move the anchorEl state to your component with AppBar and pass anchorEl and onClose to DropDownMenu as props:
function MainAppBar() {
const [anchorEl, setAnchorEl] = React.useState(null);
const handleClick = (event) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
<AppBar position="sticky" className={classes.navBarStyle}>
<Toolbar>
<Avatar alt="" className={classes.userAvatar} src="./avatar.jpg" onClick={handleClick} />
<DropDownMenu anchorEl={anchorEl} onClose={handleClose} />
</Toolbar>
</AppBar>
}
function DropDownMenu({ anchorEl, onClose }) {
const open = Boolean(anchorEl);
return (
<div>
<Menu
id="fade-menu"
anchorEl={anchorEl}
keepMounted
open={open}
onClose={onClose}
TransitionComponent={Fade}
>
<MenuItem onClick={handleClose}>Profile</MenuItem>
<MenuItem onClick={handleClose}>My account</MenuItem>
<MenuItem onClick={handleClose}>Logout</MenuItem>
</Menu>
</div>
);
}
I wrote a library, material-ui-popup-state, that is less cumbersome to use for cases like this:
import {bindTrigger, bindMenu, usePopupState} from 'material-ui-popup-state/hooks';
function MainAppBar() {
const popupState = usePopupState({ variant: 'popover', popupId: 'fade-menu' });
<AppBar position="sticky" className={classes.navBarStyle}>
<Toolbar>
<Avatar {...bindTrigger(popupState)} alt="" className={classes.userAvatar} src="./avatar.jpg" />
<DropDownMenu popupState={popupState} />
</Toolbar>
</AppBar>
}
function DropDownMenu({ popupState }) {
return (
<div>
<Menu
{...bindMenu(popupState)}
keepMounted
TransitionComponent={Fade}
>
<MenuItem onClick={popupState.close}>Profile</MenuItem>
<MenuItem onClick={popupState.close}>My account</MenuItem>
<MenuItem onClick={popupState.close}>Logout</MenuItem>
</Menu>
</div>
);
}
The trick is to place the function in the parent component that and are both children of, and pass it down as a prop, that way it can be a shared function.
https://reactjs.org/docs/faq-functions.html
You have ToolBar component which is parent of your avatar and menudropdown components. Place those handleClick, handleClose and const [anchorEl, setAnchorEl] = React.useState(null); in parent component of those childs.
Next pass handleClose funtion to dropDownMenu component and handleClick to Avatar component as props. You should also pass state to those components who base their actions on it, in this case to menuDropDowncomponent because i see that he need this state data.

React Material UI + React Router >>>> I cannot use Redirect inside a onClick function in a button

I am trying to trigger the Redirect React Dom
that is my button component in the handleMenuItemClick() function. But nothing happens.
I have tried a bunch of stuff but but still no success.
How can I make the both work together? My best try was to make a function that return the Redirect component as I saw in one post around, but still no success.
My Code:
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
import { Grid, Button, ButtonGroup, ArrowDropDownIcon, ClickAwayListener, Grow, Paper, Popper, MenuItem, MenuList, Link } from '#material-ui/core/Grid';
const SplitButton = (props) => {
const [open, setOpen] = React.useState(false);
const anchorRef = React.useRef(null);
const [selectedIndex, setSelectedIndex] = React.useState(1);
const myGroups = props.myGroups
const handleMenuItemClick = (event, index) => {
setSelectedIndex(index);
setOpen(false);
return <Redirect to={`/groups/${index}`} />
};
const handleToggle = () => {
setOpen((prevOpen) => !prevOpen);
};
const handleClose = (event) => {
if (anchorRef.current && anchorRef.current.contains(event.target)) {
return;
}
setOpen(false);
};
return (
<>
<ButtonGroup variant="contained" color="primary" ref={anchorRef} aria-label="split button">
<Button onClick={null}>My Groups</Button>
<Button
color="primary"
size="small"
aria-controls={open ? 'split-button-menu' : undefined}
aria-expanded={open ? 'true' : undefined}
aria-label="select merge strategy"
aria-haspopup="menu"
onClick={handleToggle}
>
<ArrowDropDownIcon />
</Button>
</ButtonGroup>
<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 id="split-button-menu">
{ myGroups.map((group) => (
<MenuItem
key={group.id}
onClick={(event) => handleMenuItemClick(event, group.id)}
>
{group.title}
</MenuItem>
))}
</MenuList>
</ClickAwayListener>
</Paper>
</Grow>
)}
</Popper>
</>
);
}
export default SplitButton
You can redirect user via 2 methods: useHistory or <Redirect />
useHistory hook
If you want to redirect the user directly on click, you can treat the code imperatively and tell React what to do:
const history = useHistory();
const handleMenuItemClick = (event, index) => {
setSelectedIndex(index);
setOpen(false);
history.push(`/groups/${index}`)
};
More info https://reactrouter.com/web/api/Hooks/usehistory
Redirect component
Or if you feel more comfortable using React's default declarative model, you can say what's changed and allow your code to react to this change:
const [redirectUrl, setRedirectUrl] = useState('')
const handleMenuItemClick = (event, index) => {
setSelectedIndex(index);
setOpen(false);
setRedirectUrl(`/groups/${index}`)
};
if (redirectUrl) {
return <Redirect to={redirectUrl} />
}
return (
<>
<ButtonGroup variant="contained" color="primary" ref={anchorRef} aria-label="split button">
<Button onClick={null}>My Groups</Button>
<Button
...
More info https://reactrouter.com/web/api/Redirect

Display menu on hover reactjs

In a react application, I am using reactstrap css framework to make the dropdowns to list the dropdown menus.
Example.Js
<Dropdown
className="d-inline-block"
onMouseOver={this.onMouseEnter}
onMouseLeave={this.onMouseLeave}
isOpen={this.state.dropdownOpen}
toggle={this.toggle}
>
<DropdownToggle caret>Dropdown1</DropdownToggle>
<DropdownMenu>
<DropdownItem header>Submenu 1</DropdownItem>
<DropdownItem>Submenu 1.1</DropdownItem>
</DropdownMenu>
<DropdownToggle caret>Dropdown2</DropdownToggle>
<DropdownMenu>
<DropdownItem header>Submenu 2</DropdownItem>
<DropdownItem>Submenu 2.1</DropdownItem>
<DropdownItem>Submenu 2.2</DropdownItem>
</DropdownMenu>
<br /><br />
<DropdownToggle caret>Dropdown3</DropdownToggle>
<DropdownMenu>
<DropdownItem header>Submenu 3</DropdownItem>
<DropdownItem>Submenu 3.1</DropdownItem>
<DropdownItem>Submenu 3.2</DropdownItem>
<DropdownItem>Submenu 3.3</DropdownItem>
</DropdownMenu>
</Dropdown>
Here I have made the setState to set the state for dropDownOpen in the events such as onMouseOver and onMouseLeave.
The issue is on hover of single dropdown menu, every dropdown gets opened.
Click here for Working Demo
Kindly help me to make the hover dropdown to only list the menus of hovered one and not all at a time.
Note: In my real application, these dropdown menus will be dynamic, So I cant make any hardcoded state like, dropDown1, dropDown2, dropDown3 ... etc ..
It may have any n number of dropdowns.. So please give me solution in keeping the dynamic menus into consideration.
Basically each dropdown menu needs to be in its own Dropdown composite component, with its own state and handlers. I forked and updated your demo that hopefully gives you the right idea.
<div>
<Dropdown
className="d-inline-block"
onMouseOver={this.onMouseEnter}
onMouseLeave={this.onMouseLeave}
isOpen={this.state.dropdownOpen1}
toggle={this.toggle1}
>
<DropdownToggle caret>Dropdown1</DropdownToggle>
<DropdownMenu>
<DropdownItem header>Submenu 1</DropdownItem>
<DropdownItem>Submenu 1.1</DropdownItem>
</DropdownMenu>
</Dropdown>
<Dropdown
className="d-inline-block"
// onMouseOver={this.onMouseEnter}
// onMouseLeave={this.onMouseLeave}
isOpen={this.state.dropdownOpen2}
toggle={this.toggle2}
>
<DropdownToggle caret>Dropdown2</DropdownToggle>
<DropdownMenu>
<DropdownItem header>Submenu 2</DropdownItem>
<DropdownItem>Submenu 2.1</DropdownItem>
<DropdownItem>Submenu 2.2</DropdownItem>
</DropdownMenu>
<br /><br />
</Dropdown>
<Dropdown
className="d-inline-block"
// onMouseOver={this.onMouseEnter}
// onMouseLeave={this.onMouseLeave}
isOpen={this.state.dropdownOpen3}
toggle={this.toggle3}
>
<DropdownToggle caret>Dropdown3</DropdownToggle>
<DropdownMenu>
<DropdownItem header>Submenu 3</DropdownItem>
<DropdownItem>Submenu 3.1</DropdownItem>
<DropdownItem>Submenu 3.2</DropdownItem>
<DropdownItem>Submenu 3.3</DropdownItem>
</DropdownMenu>
</Dropdown>
</div>
https://stackblitz.com/edit/reactstrap-v6-2dnzex?file=Example.js
Here is my solution to show the submenu of the menu or dropdown.
Feel free to ask any questions or if you have any confusion about it.
import React from 'react';
import { Box, jsx } from 'theme-ui';
import { Link } from 'gatsby';
import { H1 } from '../../components/ThemeHeader';
import { Image } from '../../components';
const CardWithCTALinks = (props) => {
const { cardWithCTALinksImage, ctaLinks, heading, bgColor } = props;
const [onCTAListHover, setOnCTAListHover] = React.useState({ status: '', indexAt: -1 });
const updateCTAListHover = (newOnCTAListHover, idx) => {
if (newOnCTAListHover !== onCTAListHover) setOnCTAListHover({ state: newOnCTAListHover, indexAt: idx });
};
const renderImageSection = (src, alt) => {
return <Image src={src} alt={alt} />;
};
const renderSubLinks = (subLinks, idx) => {
return (
<Box>
{idx === onCTAListHover.indexAt &&
subLinks.map((link) => (
<Link key={Math.random().toString(36).substring(7)} to="/">
{link.text}
</Link>
))}
</Box>
);
};
const renderLinksSection = (linksList, headingText) => {
return (
<Box>
{headingText && <H1>{headingText}</H1>}
{linksList && (
<Box>
{linksList.map((link, index) => (
<h1 onMouseEnter={() => updateCTAListHover(true, index)} onMouseLeave={() => updateCTAListHover(false, index)}>
{link.node?.title}
{link.node?.navItems.length > 0 && <>{onCTAListHover && renderSubLinks(link.node?.navItems, index)}</>}
</h1>
))}
</Box>
)}
</Box>
);
};
return (
<Box style={{ backgroundColor: bgColor }}>
{cardWithCTALinksImage && <Box>{renderImageSection(cardWithCTALinksImage?.asset._ref, 'alt')}</Box>}
{ctaLinks && heading && <Box>{renderLinksSection(ctaLinks.edges, heading)}</Box>}
</Box>
);
};
export default CardWithCTALinks;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
import React from "react";
import {
Dropdown,
DropdownToggle,
DropdownMenu,
DropdownItem
} from "reactstrap";
export default class Example extends React.Component {
constructor(props) {
super(props);
this.toggle = this.toggle.bind(this);
this.onMouseEnter = this.onMouseEnter.bind(this);
this.onMouseLeave = this.onMouseLeave.bind(this);
this.state = {
dropdownOpen1: false,
dropdownOpen2: false,
dropdownOpen3: false
};
}
toggle(id) {
this.setState({[id]:!this.state[`${id}`]})
}
onMouseEnter(id) {
this.setState({ [id]: true });
}
onMouseLeave(id) {
this.setState({ [id]: false });
}
render() {
return (
<div>
<Dropdown
className="d-inline-block"
onMouseOver={()=>this.onMouseEnter("dropdownOpen1")}
onMouseLeave={()=>this.onMouseLeave("dropdownOpen1")}
isOpen={this.state.dropdownOpen1}
toggle={()=>this.toggle("dropdownOpen1")}
>
<DropdownToggle caret>Dropdown1</DropdownToggle>
<DropdownMenu>
<DropdownItem header>Submenu 1</DropdownItem>
<DropdownItem>Submenu 1.1</DropdownItem>
</DropdownMenu>
</Dropdown>
<Dropdown
className="d-inline-block"
onMouseOver={()=>this.onMouseEnter("dropdownOpen2")}
onMouseLeave={()=>this.onMouseLeave("dropdownOpen2")}
isOpen={this.state.dropdownOpen2}
toggle={()=>this.toggle("dropdownOpen2")}
>
<DropdownToggle caret>Dropdown2</DropdownToggle>
<DropdownMenu>
<DropdownItem header>Submenu 2</DropdownItem>
<DropdownItem>Submenu 2.1</DropdownItem>
<DropdownItem>Submenu 2.2</DropdownItem>
</DropdownMenu>
</Dropdown>
<Dropdown
className="d-inline-block"
onMouseOver={()=>this.onMouseEnter("dropdownOpen3")}
onMouseLeave={()=>this.onMouseLeave("dropdownOpen3")}
isOpen={this.state.dropdownOpen3}
toggle={()=>this.toggle("dropdownOpen3")}
>
<br />
<br />
<DropdownToggle caret>Dropdown3</DropdownToggle>
<DropdownMenu>
<DropdownItem header>Submenu 3</DropdownItem>
<DropdownItem>Submenu 3.1</DropdownItem>
<DropdownItem>Submenu 3.2</DropdownItem>
<DropdownItem>Submenu 3.3</DropdownItem>
</DropdownMenu>
</Dropdown>
</div>
);
}
}
Try it out once !!!let me know,basically you need to pass different args for each dropdown ,so that your method can differentiate what to open or close.
Hi #TestUser and Drew Reese I have reduces some line using arrow functions: https://stackblitz.com/edit/reactstrap-dropdown?file=Example.js and if you need this for n dropdown then you can create single drop-down and using props you can create multiple dropdown for this (you can use map for repeating for this) and can use this multiple places. that is the beauty of react.
So basically you were using same state variable for all three dropdowns.
To fix this you need to maintain three different state variables, since you want to have dynamic way you can follow the below approach
Also for reusable approach you can make the Dropdown as a separate component.
You can add more logic if you want, this is the simple way to solve the problem
App.js
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
import { Container } from "reactstrap";
import CustomDropDown from "./CustomDropdown";
import "bootstrap/dist/css/bootstrap.min.css";
import "./styles.css";
const dropdownConfig = [
{
customKey: 1,
options: [
{ title: "Submenu 1", header: true },
{ title: "Submenu 1.1", header: false }
],
name: "dropdownOpen1"
},
{
customKey: 2,
options: [
{ title: "Submenu 2", header: true },
{ title: "Submenu 2.1", header: false }
],
name: "dropdownOpen2"
},
{
customKey: 3,
options: [
{ title: "Submenu 3", header: true },
{ title: "Submenu 3.1", header: false }
],
name: "dropdownOpen3"
}
];
function App() {
const [keysForDropdown, setKeysForDropdown] = useState({});
useEffect(() => {
const keys = dropdownConfig.map(dropdown => dropdown.name);
const object = keys.reduce((acc, curr) => {
acc[curr] = false;
return acc;
}, {});
setKeysForDropdown({ ...object });
}, []);
const _handleToggle = e => {
setKeysForDropdown({
...keysForDropdown,
[e.target.name]: !keysForDropdown[e.target.name]
});
};
const _handleMouseEnter = e => {
setKeysForDropdown({
...keysForDropdown,
[e.target.name]: !keysForDropdown[e.target.name]
});
};
const _handleMouseLeave = e => {
setKeysForDropdown({
...keysForDropdown,
[e.target.name]: !keysForDropdown[e.target.name]
});
};
return (
<div className="App">
<Container>
{keysForDropdown &&
dropdownConfig.map(dropdown => (
<CustomDropDown
{...dropdown}
key={dropdown.customKey}
stateKeys={keysForDropdown}
handleToggle={_handleToggle}
handleMouseEnter={_handleMouseEnter}
handleMouseLeave={_handleMouseLeave}
/>
))}
</Container>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
CustomDropdown.js
import React from "react";
import {
Dropdown,
DropdownToggle,
DropdownMenu,
DropdownItem
} from "reactstrap";
const CustomDropDown = props => {
const {
handleMouseEnter,
handleMouseLeave,
handleToggle,
options,
name,
stateKeys
} = props;
return (
<div className="dropdown-container">
<Dropdown
className="d-inline-block"
onMouseOver={handleMouseEnter}
onMouseLeave={handleMouseLeave}
isOpen={stateKeys[name]}
toggle={handleToggle}
>
<DropdownToggle name={name} caret>
Dropdown1
</DropdownToggle>
<DropdownMenu>
{options.length &&
options.map(({ header, title }) => (
<DropdownItem header={header}>{title}</DropdownItem>
))}
</DropdownMenu>
</Dropdown>
</div>
);
};
export default CustomDropDown;
Working codesandbox

Not able to copy to clipboard from Popper which is inside Dialogue in material-UI

Steps to reproduce the bug here: (Try to open in Firefox, I was almost crashed the chrome :P) https://codesandbox.io/s/73z5293391
Click on the OPEN SIMPLE DIALOGUE button. Now you see a dialogue, Click on the TOGGLE POPPER button.
Now you see a Popper which has an input box and a COPY button.
You need to copy on the text inside the input box in this case hello.
So I am not able to copy to clipboard actually.
First I thought it might be a problem with Dialogue. But no. In just a Dialogue it works. But not on Popper which pops up from the Dialogue(Only for Popper also it works).
Can you help me to copy to clipboard in this situation?
Once again the source code:
import React from "react";
import PropTypes from "prop-types";
import { withStyles } from "#material-ui/core/styles";
import Button from "#material-ui/core/Button";
import Avatar from "#material-ui/core/Avatar";
import List from "#material-ui/core/List";
import ListItem from "#material-ui/core/ListItem";
import ListItemAvatar from "#material-ui/core/ListItemAvatar";
import ListItemText from "#material-ui/core/ListItemText";
import DialogTitle from "#material-ui/core/DialogTitle";
import Dialog from "#material-ui/core/Dialog";
import PersonIcon from "#material-ui/icons/Person";
import AddIcon from "#material-ui/icons/Add";
import Typography from "#material-ui/core/Typography";
import blue from "#material-ui/core/colors/blue";
import DialogContent from "#material-ui/core/DialogContent";
import Popper from "#material-ui/core/Popper";
const emails = ["username#gmail.com", "user02#gmail.com"];
const styles = {
avatar: {
backgroundColor: blue[100],
color: blue[600]
}
};
class SimpleDialog extends React.Component {
state = {
anchorEl: null,
openPopper: false
};
handleClose = () => {
this.props.onClose(this.props.selectedValue);
};
handleListItemClick = value => {
this.props.onClose(value);
};
copytoClipBoard = () => {
this.hello.select();
try {
return document.execCommand("copy");
} catch (err) {
console.log("Oops, unable to copy");
}
};
handleClick = event => {
const { currentTarget } = event;
this.setState(state => ({
anchorEl: currentTarget,
openPopper: !state.openPopper
}));
};
render() {
const { classes, onClose, selectedValue, ...other } = this.props;
const { anchorEl, openPopper } = this.state;
const id = openPopper ? "simple-popper" : null;
return (
<Dialog
onClose={this.handleClose}
aria-labelledby="simple-dialog-title"
{...other}
>
<DialogTitle id="simple-dialog-title">Set backup account</DialogTitle>
<DialogContent>
<Button
aria-describedby={id}
variant="contained"
onClick={this.handleClick}
>
Toggle Popper
</Button>
<Popper
id={id}
open={openPopper}
anchorEl={anchorEl}
style={{ zIndex: 10000 }}
>
<input
value="hello"
readOnly
type="text"
ref={node => (this.hello = node)}
/>
<Button onClick={this.copytoClipBoard}> Copy </Button>
</Popper>
<List>
{emails.map(email => (
<ListItem
button
onClick={() => this.handleListItemClick(email)}
key={email}
>
<ListItemAvatar>
<Avatar className={classes.avatar}>
<PersonIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary={email} />
</ListItem>
))}
<ListItem
button
onClick={() => this.handleListItemClick("addAccount")}
>
<ListItemAvatar>
<Avatar>
<AddIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary="add account" />
</ListItem>
</List>
</DialogContent>
</Dialog>
);
}
}
SimpleDialog.propTypes = {
classes: PropTypes.object.isRequired,
onClose: PropTypes.func,
selectedValue: PropTypes.string
};
const SimpleDialogWrapped = withStyles(styles)(SimpleDialog);
class SimpleDialogDemo extends React.Component {
state = {
open: false,
selectedValue: emails[1]
};
handleClickOpen = () => {
this.setState({
open: true
});
};
handleClose = value => {
this.setState({ selectedValue: value, open: false });
};
render() {
return (
<div>
<Typography variant="subtitle1">
Selected: {this.state.selectedValue}
</Typography>
<br />
<Button
variant="outlined"
color="primary"
onClick={this.handleClickOpen}
>
Open simple dialog
</Button>
<SimpleDialogWrapped
selectedValue={this.state.selectedValue}
open={this.state.open}
onClose={this.handleClose}
/>
</div>
);
}
}
export default SimpleDialogDemo;
#akhila-hegde You can add disablePortal to the Popper to solve this.
Note that problem is not with Copy to clipboard. Problem is that you are not able to select the text in the field (because it is in a Portal).
Here is Sandbox link with disablePortal set to true - https://codesandbox.io/s/lxjwj3p8m9

Categories