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.
Related
I have a functional component Profile.js and on the header of that screen I put a button. Once this button is pressed I want to display a dropdown menu from react-native-material-menu
Part of my Profile.js:
function Profile(props) {
useLayoutEffect(() => {
props.navigation.setOptions({
headerRight: () => <HeaderRight />,
});
}
}, []);
My HeaderRight component:
export const HeaderRight = () => {
return (
<TouchableOpacity style={{ marginTop: 20, marginRight: 8 }}>
<Feather
name="more-vertical"
size={24}
color="black"
onPress={() => renderMenu()}
/>
</TouchableOpacity>
);
};
As you can see when the icon is pressed I call my renderMenu(), which looks like the following:
import React, { useState } from "react";
import { View, Text } from "react-native";
import { Menu, MenuItem, MenuDivider } from "react-native-material-menu";
export const renderMenu = (props) => {
const [visible, setVisible] = useState(true);
const hideMenu = () => setVisible(false);
const showMenu = () => setVisible(true);
return (
<View
style={{ height: "100%", alignItems: "center", justifyContent: "center" }}
>
<Menu
visible={visible}
anchor={<Text onPress={showMenu}>Show menu</Text>}
onRequestClose={hideMenu}
>
<MenuItem onPress={hideMenu}>Menu item 1</MenuItem>
<MenuItem onPress={hideMenu}>Menu item 2</MenuItem>
<MenuItem disabled>Disabled item</MenuItem>
<MenuDivider />
<MenuItem onPress={hideMenu}>Menu item 4</MenuItem>
</Menu>
</View>
);
};
When I render my page, I click on the header icon and I get the following error:
Error: Invalid hook call. Hooks can only be called inside of the body
of a function component. This could happen for one of the following
reasons:
You might have mismatching versions of React and the renderer (such as React DOM)
You might be breaking the Rules of Hooks
You might have more than one copy of React in the same app
Can you tell me, how do I render my dropdown menu component?
You do not render the JSX on the return of an onPress function. Try this out:
export const HeaderRight = () => {
const [visible, setVisible] = useState(false);
let toggle = () => setVisible(!visible);
return (
<>
<TouchableOpacity style={{ marginTop: 20, marginRight: 8 }}>
<Feather
name="more-vertical"
size={24}
color="black"
onPress={toggle}
/>
</TouchableOpacity>
<Menu
visible={visible}
anchor={<Text onPress={toggle}>Show menu</Text>}
onRequestClose={toggle}>
<MenuItem onPress={toggle}>Menu item 1</MenuItem>
<MenuItem onPress={toggle}>Menu item 2</MenuItem>
<MenuItem disabled>Disabled item</MenuItem>
<MenuDivider />
<MenuItem onPress={toggle}>Menu item 4</MenuItem>
</Menu>
</>
);
};
I am very new in React and maybe someone could help me.
I want to toggle my Sidebar in my Header Component with Hooks.
This is my Code:
Sidebar.js
...imported things
...styles
export const SideBar = () => {
const history = useHistory();
return (
<StyledSideBar>
<Tooltip title="Dashboard" placement="right" arrow >
<StyledButton onClick={() => history.push('/dashboard')}>
<FontAwesomeIcon icon={faTachometerAltFast} />
</StyledButton>
</Tooltip >
<Tooltip title="Chat" placement="right" arrow>
<StyledButton onClick={() => history.push('/chat')}>
<FontAwesomeIcon icon={faCommentsAlt} />
</StyledButton>
</Tooltip>
<Tooltip title="Calendar" placement="right" arrow>
<StyledButton onClick={() => history.push('/calendar')}>
<FontAwesomeIcon icon={faCalendarAlt} />
</StyledButton>
</Tooltip>
</StyledSideBar>
);
}
export default SideBar;
Header.js
...imported things
...styles
import SideBar from '../sidebar';
export const MainHeader = () => {
const [show, setShow] = React.useState(false);
const toggle = () => setShow(!show);
return (
<AppBar elevation={2} position="sticky" color="inherit" >
<FlexToolbar variant="regular">
<StyledMenuIcon open={show} onClick={toggle.{SideBar}>
<FontAwesomeIcon icon={faBars} />
</StyledMenuIcon>
<Logo src="/assets/images/logo.svg" alt="Vetera Sky" />
<BreakpointLogo src="/assets/images/get-started-icon.svg" alt="Vetera Sky" />
<Spacer />
<LogoutButton onClick={onLogout}>
<FontAwesomeIcon icon={faPowerOff} />
<Typography variant="label" color={actionSecondary}>Logout</Typography>
</LogoutButton>
<BreakpointLogoutButton onClick={onLogout}>
<FontAwesomeIcon icon={faPowerOff} />
</BreakpointLogoutButton>
</FlexToolbar>
</AppBar>
)};
export default MainHeader;
I know this is wrong, but i could not find anything in the Web or in here.
If the StyledMenuIcon is clicked once, the Sidebar should open on the left and if clicked again it should close.
Hope someone can help me soon :)
You should move your state higher up in the tree where both MainHeader and SideBar components can use it:
function App() {
const [showSidebar, setShowSidebar] = useState(false);
const onToggleSidebar = () => {
setShowSidebar(!showSidebar);
};
return (
<MainHeader onToggleSidebar={onToggleSidebar} />
<SideBar open={showSidebar}
);
}
If the components aren't available on the same level in the tree, you could use React's context api to use this logic from different places down the tree.
Or if you're already using something like redux or another state management system you could move your app state there.
I have a header component separate from a Register dialog modal component. So like a parent an child components. I want to call the Register dialog (child) from the headerlink component (parent)
Here's my headerlinks component:
...
import Register from "../Register";
....
export default function HeaderLinks(props) {
const classes = useStyles();
const [open, setOpen] = React.useState(false);
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
return (
<List className={classes.list}>
<ListItem className={classes.listItem}>
<Button
color="transparent"
className={classes.navLink}
onClick={handleClickOpen}
>
Register
</Button>
</ListItem>
<ListItem className={classes.listItem}>
<Button
color="transparent"
className={classes.navLink}
>
Log In
</Button>
</ListItem>
</List>
);
<Register open={handleClickOpen} onClose={handleClose} />;
}
The Register dialog component was simply copied from Material-UI's documentation and removed the button.
export default function Register() {
const [open, setOpen] = React.useState(false);
const classes = useStyles();
const theme = useTheme();
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
return (
<div>
<Dialog
onClose={handleClose}
aria-labelledby="customized-dialog-title"
open={open}
>
<DialogTitle id="customized-dialog-title" onClose={handleClose}>
Register
</DialogTitle>
<DialogContent dividers>
...content...
</DialogContent>
</Dialog>
</div>
);
}
When I click the Register button on the headerLink, nothing happens. Not sure what else I'm missing because there are no errors.
On your HeaderLinks, you've already returned a value on the function so the rest of source code below that will not execute.
return (
<List className={classes.list}>
...
</List>
);
<Register open={handleClickOpen} onClose={handleClose} />; // <-- code will not be reached
So just move that inside of the return statement and close them on a single tag because adjacent JSX elements must be wrapped in an enclosing tag. Pass down props to Register, it does not need an internal state since its open prop (according to your design it seems) is to be controlled by HeaderLinks component
export default function HeaderLinks() {
const classes = useStyles();
const [open, setOpen] = React.useState(false);
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
return (
<>
<List className={classes.list}>
<ListItem className={classes.listItem}>
<Button
color="transparent"
className={classes.navLink}
onClick={handleClickOpen}
>
Register
</Button>
</ListItem>
<ListItem className={classes.listItem}>
<Button color="transparent" className={classes.navLink}>
Log In
</Button>
</ListItem>
</List>
<Register open={open} handleClose={handleClose} />
</>
);
}
function Register({ open, handleClose }) {
const classes = useStyles();
const theme = useTheme();
return (
<div>
<Dialog
onClose={handleClose}
aria-labelledby="customized-dialog-title"
open={open}
>
<DialogTitle id="customized-dialog-title" onClose={handleClose}>
Register
</DialogTitle>
<DialogContent dividers>...content...</DialogContent>
</Dialog>
</div>
);
}
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;
I created a navigation bar from the material-ui website and I have a onClick that when the user clicks the icon button the navigation will get redirected to a new page and I would like for the navigation to close afterwards. I'v tried different things, but for some reason it will not close.
The only thing that it does now is gets redirected to a new page and the navigation drawer continues to stay open.
I have a a function called handleDrawerClose() that closes the drawer, a const called navigation that creates the text and components and I created a const called handleNavigation that pushes the links, which makes the page redirect. Is there a way to call both of these someway. Thank you.
Below is my code:
const navigation = [
{ to: '/', text: 'Upload', Icon: InboxIcon },
{ to: '/email', text: 'Send', Icon: MailIcon }
]
const NavLinks = ({ links, onClick }) => {
const _onClick = to => () => onClick(to);
return (
<List>
{links.map(({ to, text, Icon }) => (
<ListItem key={to} button onClick={_onClick(to)}>
<ListItemIcon>
<Icon />
</ListItemIcon>
<ListItemText primary={text} />
</ListItem>
))}
</List>
)
}
const IconArrow = ({ onClick }) => {
return (
<IconButton onClick={onClick}>
<ChevronLeftIcon />
</IconButton>
)
}
export default withRouter(({ history }) => {
const classes = useStyles();
const [_, { logout }] = useAppAuth();
const [open, setOpen] = React.useState(false);
const [state, setState] = React.useState({
left: false,
});
function handleDrawerOpen() {
setOpen(true);
}
function handleDrawerClose() {
setOpen(false);
}
const handleNavigation = to => () => history.push(to);
return (
<Fragment>
<AppBar position='static'>
<Toolbar className={classes.toolbar}>
<IconButton
color='inherit'
edge='start'
aria-label='open menu drawer'
onClick={handleDrawerOpen}
>
<MenuIcon />
</IconButton>
<Link to='/'>
<img alt='Logo' src={logo} className={classes.image} />
</Link>
<Typography variant='h6' className={classes.title}>
{process.env.REACT_APP_NAME}
</Typography>
<Tooltip title='Logout'>
<Link to='/login'>
<IconButton onClick={logout} className={classes.logout}>
<LogoutIcon />
</IconButton>
</Link>
</Tooltip>
</Toolbar>
</AppBar>
<Drawer
className={classes.drawer}
variant='temporary'
anchor='left'
open={open}
>
<div className={classes.iconArrow}>
<IconArrow onClick={handleDrawerClose} />
</div>
<Divider />
<NavLinks
links={navigation}
onClick={() => handleNavigation}
/>
</Drawer>
</Fragment>
)
})
Why dont you just close it in the handler?
const handleNavigation = to => {
setOpen(false); // add this
history.push(to);
}