I have a tab component in Material-UI and I want to implement a tooltip on it.
My problem is that when I click the tab component, the tooltip is not disappearing. It must disappear after I click on that tab.
Currently, it continues to be visible even after I click on the tab.
How do I rectify that?
<Tabs
className="navbar-routes"
value={value}
style={{ color: 'green'}}
indicatorColor="secondary"
onChange={handleChange}
>
{
tabsData.map(({id,title,description}) => {
return(
<ToolTip description={description}>
<Tab
style={{
minWidth: 10,
fontSize: '80%',
fontWeight: 'bold',
marginLeft: '-4px',
marginRight: 4
}}
key={id}
component={Link}
to={`/${title}`}
label={`${title}`}
/>
</ToolTip>
);
}
)}
</Tabs>
If you look at the document of Material-UI tooltip API
You would find a props named disableHoverListener
bool
default: false
Do not respond to hover events.
Set it as True would turn off the tooltip onMouseOver event trigger.
Update
Or you can simply make it totally under control.
By binding the onClick, onMouseOver, onMouseLeave, open to related component.
import React, { useState } from "react";
import "./styles.css";
import { Tooltip, Tab } from "#material-ui/core";
export default function App() {
const [flg, setFlg] = useState(false);
const [isHover, setIsHover] = useState(false);
return (
<div className="App">
<Tooltip
title={"message"}
aria-label="add"
placement="bottom"
open={!flg && isHover}
>
<Tab
label={`Click: ${!flg ? "enabled" : "disabled"}`}
onClick={() => setFlg(!flg)}
onMouseOver={() => setIsHover(true)}
onMouseLeave={() => setIsHover(false)}
/>
</Tooltip>
</div>
);
}
Try it online:
You can also implement a generic tooltip with a managed state when to open/close the tooltip.
import Tooltip, { TooltipProps } from "#mui/material/Tooltip";
import { useState } from "react";
/**
* MUI Tooltip wrapper with adaption to the move away once focuses left.
*/
export function ManagedTooltip(props: TooltipProps) {
const [open, setOpen] = useState<boolean>(false);
// Wrap Tooltip with div to capture mouse events
return <div style={{ display: 'flex' }}
onMouseEnter={() => setOpen(true)}
onMouseLeave={() => setOpen(false)}
onClick={() => setOpen(false)}
>
{/* Show the original MUI Tooltip with all props. */}
{/* Just override the open attribute to be fully managed, and disable internal listeners */}
<Tooltip {...props} open={open} disableHoverListener disableFocusListener />
</div>;
}
Once it's ready, you can use it anywhere exactly like the original MUI tooltip.
<Tabs
className="navbar-routes"
value={value}
style={{ color: 'green'}}
indicatorColor="secondary"
onChange={handleChange}
>
{
tabsData.map(({id,title,description}) => {
return(
<ManagedTooltip description={description}>
<Tab
style={{
minWidth: 10,
fontSize: '80%',
fontWeight: 'bold',
marginLeft: '-4px',
marginRight: 4
}}
key={id}
component={Link}
to={`/${title}`}
label={`${title}`}
/>
</ManagedTooltip>
);
}
)}
</Tabs>
The way I solved this was by rendering the tooltip conditionally. In your case I suppose you want the tooltip not to render for the tab of the current active route:
function ConditionalTooltip({renderTooltip, children, ...props}) {
return renderTooltip ? <Tooltip {...props}>{children}</Tooltip> : children;
}
function Tabs() {
const location = useLocation();
return (
<Tabs
className="navbar-routes"
value={value}
style={{ color: 'green'}}
indicatorColor="secondary"
onChange={handleChange}
>
{
tabsData.map(({id,title,description}) => {
return(
<ConditionalTooltip
renderTooltip={location.pathname.indexOf(title) === -1} /* only render tooltip on not active urls */
title={description}
>
<Tab
style={{
minWidth: 10,
fontSize: '80%',
fontWeight: 'bold',
marginLeft: '-4px',
marginRight: 4
}}
key={id}
component={Link}
to={`/${title}`}
label={`${title}`}
/>
</ConditionalTooltip>
);
}
)}
</Tabs>
)
}
Related
Trying to use tabs as links but I can't find any where that explains how to do it when you are styling a tab component. I get the syntax that if you use a Tab component you should do this:
<Tab value="..." component={Link} to="/" >
However if I have styled the Tab before and created an additional component like below, I can't find anything. I did this because I couldn't style the tab directly and I found this solution.
//Styled Tab
const StyledButton = styled(Button)(() => ({
...theme.typography.estimateBtn,
borderRadius: "50px",
marginLeft: "25px",
marginRight: "25px",
height: "50px",
}));
// nav container
const ToolBarCom: React.FC = () => {
const [value, setValue] = React.useState(0);
// handles classes for non react elements
const classes = useStyles();
const onChangeHandler = (e: React.SyntheticEvent, value: number) => {
setValue(value);
};
return (
<ToolBar disableGutters>
{/* <Typography variant='h3'>Arc Development</Typography> */}
<img src={logo} alt='company logo' className={classes.logo} />
<Tabs
value={value}
onChange={onChangeHandler}
textColor='secondary'
indicatorColor='primary'
className={classes.tabsContainer}
variant='scrollable'
scrollButtons='auto'>
<StyledTab value={0} label='Home' />
<StyledTab value={1} label='Services' />
<StyledTab value={2} label=' The Revolution' />
<StyledTab value={3} label='About Us' />
<StyledTab value={4} label=' Contact Us' />
</Tabs>
<StyledButton variant='contained' color='secondary'>
Free Trial
</StyledButton>
</ToolBar>
);
I had to restyle the Tab with useStyle hook. You have to find the root of the tab component. I couldn't find any documentation for the issue above. See below:
const useStyles = makeStyles(() => ({
logo: {
height: "5em",
},
tabsContainer: {
marginLeft: "auto",
color: "white",
},
tab: {
"&.MuiTab-root": {
...theme.typography.tab,
minWidth: 10,
marginLeft: "25px",
},
},
}));
I have mui v5 dialog that I am not able to set its width using style() component.
import {
Dialog,
DialogContent,
DialogTitle,
Paper,
Typography,
} from "#mui/material";
import { Close } from "#mui/icons-material";
import { styled } from "#mui/material/styles";
import ActionButton from "./ActionButton";
import React from "react";
const StyledDialog = styled(Dialog)(({ theme }) => ({
fullWidth: true, // it's not taking effect
maxWidth: "lg", // it's not taking effect
padding: theme.spacing(2),
position: "absolute",
top: theme.spacing(5),
"& MuiDialog-paper": {
padding: theme.spacing(2),
position: "absolute",
top: theme.spacing(5),
},
"& .MuiTypography-h6": {
paddingRight: "0px",
},
}));
export default function Popup(props) {
const { title, children, openPopup, setOpenPopup } = props;
return (
<StyledDialog open={openPopup} onClose={() => setOpenPopup(false)}>
<DialogTitle>
<div style={{ display: "flex" }}>
<Typography variant="h6" component="div" style={{ flexGrow: 1 }}>
{title}
</Typography>
<ActionButton
color="secondary"
onClick={() => setOpenPopup(false)}
>
<Close />
</ActionButton>
</div>
</DialogTitle>
<DialogContent dividers>{children}</DialogContent>
</StyledDialog>
However, if I listed the props directly inside the it works.
<StyledDialog fullWidth="true" maxWidth="lg">
</StyledDialog>
What is the correct way of setting up the width and maxWidth.
Try this on the styledDialog declaration:
const StyledDialog = styled((props) => (
<Dialog
fullWidth={true}
maxWidth={'lg'}
{...props}
/>
))(({ theme }) => ({
// Your code to style the dialog goes here
}));
The problem on your code is that you arent passing the properties fullWidth and maxWidth to the component.
You are trying to use maxWidth: lg and fullWidth: true like css, but they are only for the Dialog API.
So you could use maxWidth in the component as an API like
<Dialog maxWidth="lg" fullWidth ... >
or you can add it as css style using theme.
const StyledDialog = styled(Dialog)(({ theme }) => ({
maxWidth: theme.breakpoints.values.lg, // there's no styling such fullWidth
...
}));
You could have a look at the default theme.
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 have a dynamically generated set of dropdowns and accordions that populate at render client-side (validated user purchases from db).
I'm running into an error that I'm sure comes from my menu anchorEl not knowing 'which' menu to open using anchorEl. The MUI documentation doesn't really cover multiple dynamic menus, so I'm unsure of how to manage which menu is open
Here is a pic that illustrates my use-case:
As you can see, the menu that gets anchored is actually the last rendered element. Every download button shows the last rendered menu. I've done research and I think I've whittled it down to the anchorEl and open props.
Here is my code. Keep in mind, the data structure is working as intended, so I've omitted it to keep it brief, and because it's coming from firebase, I'd have to completely recreate it here (and I think it's redundant).
The component:
import { useAuth } from '../contexts/AuthContext'
import { Accordion, AccordionSummary, AccordionDetails, Button, ButtonGroup, CircularProgress, ClickAwayListener, Grid, Menu, MenuItem, Typography } from '#material-ui/core'
import { ExpandMore as ExpandMoreIcon } from '#material-ui/icons'
import LoginForm from '../components/LoginForm'
import { motion } from 'framer-motion'
import { useEffect, useState } from 'react'
import { db, functions } from '../firebase'
import styles from '../styles/Account.module.scss'
export default function Account() {
const { currentUser } = useAuth()
const [userPurchases, setUserPurchases] = useState([])
const [anchorEl, setAnchorEl] = useState(null)
const [generatingURL, setGeneratingURL] = useState(false)
function openDownloads(e) {
setAnchorEl(prevState => (e.currentTarget))
}
function handleClose(e) {
setAnchorEl(prevState => null)
}
function generateLink(prefix, variationChoice, pack) {
console.log("pack from generate func", pack)
setGeneratingURL(true)
const variation = variationChoice ? `${variationChoice}/` : ''
console.log('link: ', `edit-elements/${prefix}/${variation}${pack}.zip`)
setGeneratingURL(false)
return
if (pack.downloads_remaining === 0) {
console.error("No more Downloads remaining")
setGeneratingURL(false)
handleClose()
return
}
handleClose()
const genLink = functions.httpsCallable('generatePresignedURL')
genLink({
fileName: pack,
variation: variation,
prefix: prefix
})
.then(res => {
console.log(JSON.stringify(res))
setGeneratingURL(false)
})
.catch(err => {
console.log(JSON.stringify(err))
setGeneratingURL(false)
})
}
useEffect(() => {
if (currentUser !== null) {
const fetchData = async () => {
// Grab user products_owned from customers collection for user UID
const results = await db.collection('customers').doc(currentUser.uid).get()
.then((response) => {
return response.data().products_owned
})
.catch(err => console.log(err))
Object.entries(results).map(([product, fields]) => {
// Grabbing each product document to get meta (title, prefix, image location, etc [so it's always current])
const productDoc = db.collection('products').doc(product).get()
.then(doc => {
const data = doc.data()
const productMeta = {
uid: product,
title: data.title,
main_image: data.main_image,
product_prefix: data.product_prefix,
variations: data.variations
}
// This is where we merge the meta with the customer purchase data for each product
setUserPurchases({
...userPurchases,
[product]: {
...fields,
...productMeta
}
})
})
.catch(err => {
console.error('Error retrieving purchases. Please refresh page to try again. Full error: ', JSON.stringify(err))
})
})
}
return fetchData()
}
}, [currentUser])
if (userPurchases.length === 0) {
return (
<CircularProgress />
)
}
return(
currentUser !== null && userPurchases !== null ?
<>
<p>Welcome, { currentUser.displayName || currentUser.email }!</p>
<Typography variant="h3" style={{marginBottom: '1em'}}>Purchased Products:</Typography>
{ userPurchases && Object.values(userPurchases).map((product) => {
const purchase_date = new Date(product.purchase_date.seconds * 1000).toLocaleDateString()
return (
<motion.div key={product.uid}>
<Accordion style={{backgroundColor: '#efefef'}}>
<AccordionSummary expandIcon={<ExpandMoreIcon style={{fontSize: "calc(2vw + 10px)"}}/>} aria-controls={`${product.title} accordion panel`}>
<Grid container direction="row" alignItems="center">
<Grid item xs={3}><img src={product.main_image} style={{ height: '100%', maxHeight: "200px", width: '100%', maxWidth: '150px' }}/></Grid>
<Grid item xs={6}><Typography variant="h6">{product.title}</Typography></Grid>
<Grid item xs={3}><Typography variant="body2"><b>Purchase Date:</b><br />{purchase_date}</Typography></Grid>
</Grid>
</AccordionSummary>
<AccordionDetails style={{backgroundColor: "#e5e5e5", borderTop: 'solid 6px #5e5e5e', padding: '0px'}}>
<Grid container direction="column" className={styles[`product-grid`]}>
{Object.entries(product.packs).map(([pack, downloads]) => {
// The pack object right now
return (
<Grid key={ `${pack}-container` } container direction="row" alignItems="center" justify="space-between" style={{padding: '2em 1em'}}>
<Grid item xs={4} style={{ textTransform: 'uppercase', backgroundColor: 'transparent' }}><Typography align="left" variant="subtitle2" style={{fontSize: 'calc(.5vw + 10px)'}}>{pack}</Typography></Grid>
<Grid item xs={4} style={{ backgroundColor: 'transparent' }}><Typography variant="subtitle2" style={{fontSize: "calc(.4vw + 10px)"}}>{`Remaining: ${downloads.downloads_remaining}`}</Typography></Grid>
<Grid item xs={4} style={{ backgroundColor: 'transparent' }}>
<ButtonGroup variant="contained" fullWidth >
<Button id={`${pack}-btn`} disabled={generatingURL} onClick={openDownloads} color='primary'>
<Typography variant="button" style={{fontSize: "calc(.4vw + 10px)"}} >{!generatingURL ? 'Downloads' : 'Processing'}</Typography>
</Button>
</ButtonGroup>
<ClickAwayListener key={`${product.product_prefix}-${pack}`} mouseEvent='onMouseDown' onClickAway={handleClose}>
<Menu anchorOrigin={{ vertical: 'top', horizontal: 'right' }} transformOrigin={{ vertical: 'top', horizontal: 'right' }} id={`${product}-variations`} open={Boolean(anchorEl)} anchorEl={anchorEl}>
{product.variations && <MenuItem onClick={() => generateLink(product.product_prefix, null, pack) }>{`Pack - ${pack}`}</MenuItem>}
{product.variations && Object.entries(product.variations).map(([variation, link]) => {
return (
<MenuItem key={`${product.product_prefix}-${variation}-${pack}`} onClick={() => generateLink(product.product_prefix, link, pack)}>{ variation }</MenuItem>
)
})}
</Menu>
</ClickAwayListener>
</Grid>
</Grid>
)}
)}
</Grid>
</AccordionDetails>
</Accordion>
</motion.div>
)
})
}
</>
:
<>
<p>No user Signed in</p>
<LoginForm />
</>
)
}
I think it also bears mentioning that I did check the rendered HTML, and the correct lists are there in order - It's just the last one assuming the state. Thanks in advance, and please let me know if I've missed something, or if I can clarify in any way. :)
i couldn't manage to have a menu dynamic,
instead i used the Collapse Panel example and there i manipulated with a property isOpen on every item of the array.
Check Cards Collapse Example
On the setIsOpen method you can change this bool prop:
const setIsOpen = (argNodeId: string) => {
const founded = tree.find(item => item.nodeId === argNodeId);
const items = [...tree];
if (founded) {
const index = tree.indexOf(founded);
founded.isOpen = !founded.isOpen;
items[index]=founded;
setTree(items);
}
};
<IconButton className={clsx(classes.expand, {
[classes.expandOpen]: node.isOpen,
})}
onClick={()=>setIsOpen(node.nodeId)}
aria-expanded={node.isOpen}
aria-label="show more"
>
<MoreVertIcon />
</IconButton>
</CardActions>
<Collapse in={node.isOpen} timeout="auto" unmountOnExit>
<CardContent>
<MenuItem onClick={handleClose}>{t("print")}</MenuItem>
<MenuItem onClick={handleClose}>{t("commodities_management.linkContainers")}</MenuItem>
<MenuItem onClick={handleClose}>{t("commodities_management.linkDetails")}</MenuItem>
</CardContent>
</Collapse>
I think this is the right solution for this: https://stackoverflow.com/a/59531513, change the anchorEl for every Menu element that you render. :D
This code belongs to TS react if you are using plain JS. Then remove the type.
import Menu from '#mui/material/Menu';
import MenuItem from '#mui/material/MenuItem';
import { useState } from 'react';
import { month } from '../../helper/Utilities';
function Company() {
const [anchorEl, setAnchorEl] = useState<HTMLElement[]>([]);
const handleClose = (event: any, idx: number) => {
let array = [...anchorEl];
array.splice(idx, 1);
setAnchorEl(array);
};
<div>
{month &&
month.map((val: any, ind: number) => {
return (
<div
key={val.id + 'w9348w344ndf allBankAndCardAccountOfClient'}
style={{ borderColor: ind === 0 ? '#007B55' : '#919EAB52' }}
>
<Menu
id='demo-positioned-menu'
aria-labelledby='demo-positioned-button'
anchorEl={anchorEl[ind]}
open={anchorEl[ind] ? true : false}
key={val.id + 'w9348w344ndf allBankAndCardAccountOfClient' + ind}
onClick={(event) => handleClose(event, ind)}
anchorOrigin={{
vertical: 'top',
horizontal: 'left',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'left',
}}
>
<MenuItem
key={val.id + 'w9348w344ndf allBankAndCardAccountOfClient' + ind}
onClick={(event) => handleClose(event, ind)}
style={{
display: ind === 0 ? 'none' : 'inline-block',
}}
>
<span
style={{
marginLeft: '.5em',
color: 'black',
background: 'inherit',
}}
>
Make Primary
</span>
</MenuItem>
<MenuItem onClick={(event) => handleClose(event, ind)}>
<span style={{ marginLeft: '.5em', color: 'black' }}>Edit</span>
</MenuItem>
<MenuItem
onClick={(event) => handleClose(event, ind)}
style={{
display: ind === 0 ? 'none' : 'inline-block',
}}
>
<span style={{ marginLeft: '.5em', color: 'red' }}>Delete</span>
</MenuItem>
</Menu>
</div>
);
})}
</div>;
}
export default Company;
I'm implementing a menu that opens when the user clicks on an Avatar. The problem is that the menu is rendering in a completely different place:
The avatar is the green "OB" button on the right. There is no console error and inspecting the Popover element, it's receiving the anchorEl prop:
The language menu, on the right of the avatar, renders just fine, opening where it should open. My code seems fine, I'm really not sure why the position is wrong:
export function DashboardNavbar({ setDrawer }) {
// translation hook
const { i18n } = useTranslation("navbar");
// config drawer state
const [configDrawer, setConfigDrawer] = useState(false);
// config menu state
const configMenuState = usePopupState({
variant: "popover",
popupId: "configMenu"
});
// avatar id
const [cookie] = useCookies("userInfo");
const decodedToken = decodeToken(cookie.userInfo.token);
const avatarId =
decodedToken.firstName.charAt(0) + decodedToken.lastName.charAt(0);
function DesktopNavbar() {
return (
<>
<StyledDashboardNavbar>
<Container maxWidth="lg">
<div
style={{
display: "flex",
justifyContent: "flex-end"
}}
>
<Avatar
style={{
backgroundColor:
theme.palette.secondary.main
}}
{...bindTrigger(configMenuState)}
>
{avatarId}
</Avatar>
<DashboardMenu
bindMenu={bindMenu}
menuState={configMenuState}
/>
<LanguageMenu i18n={i18n} />
</div>
</Container>
</StyledDashboardNavbar>
</>
);
}
function MobileNavbar() {
return (
<>
<StyledDashboardNavbar>
<Container maxWidth="md">
<div className="navbar">
<div
style={{
display: "flex",
alignItems: "center"
}}
>
<MenuIcon
color="secondary"
onClick={() => setDrawer(true)}
/>
</div>
<div
className="logo"
onClick={() => setConfigDrawer(true)}
>
<Avatar
style={{
backgroundColor:
theme.palette.secondary.main
}}
>
{avatarId}
</Avatar>
</div>
</div>
</Container>
</StyledDashboardNavbar>
<AvatarDrawer
drawer={configDrawer}
setDrawer={setConfigDrawer}
/>
</>
);
}
return window.innerWidth > 480 ? <DesktopNavbar /> : <MobileNavbar />;
}
I'm using the material-ui-popup-state, but I tried implementing "on-hand" without this package and the result was the same.
Any help on this is appreciated. Thanks in advance
The problem is the nesting of DesktopNavbar within DashboardNavbar. This means that every time DashboardNavbar re-renders, DesktopNavbar will be redefined. Since DesktopNavbar will be a new function compared to the previous render of DashboardNavbar, React will not recognize it as the same component type and DesktopNavbar will be re-mounted rather than just re-rendered. Since the menu state is maintained within DashboardNavbar, opening the menu causes a re-render of DashboardNavbar and therefore a re-definition of DesktopNavbar so, due to the re-mounting of DesktopNavbar and everything inside it, the anchor element passed to the menu will no longer be part of the DOM.
It is almost always a bad idea to nest the definitions of components, because the nested components will be treated as a new element type with each re-render of the containing component.
From https://reactjs.org/docs/reconciliation.html#elements-of-different-types:
Whenever the root elements have different types, React will tear down the old tree and build the new tree from scratch. Going from <a> to <img>, or from <Article> to <Comment>, or from <Button> to <div> - any of those will lead to a full rebuild.
When you redefine DesktopNavbar and MobileNavbar on re-render of DashboardNavbar, the entire tree of DOM elements within those will be removed from the DOM and re-created from scratch rather than just applying changes to the existing DOM elements. This has a big performance impact and also causes behavior issues like the one you experienced where elements that you are referring to are unexpectedly no longer part of the DOM.
If you instead move DesktopNavbar and MobileNavbar to the top-level and pass any dependencies from DashboardNavbar as props, this will cause DesktopNavbar to be recognized by React as a consistent component type across re-renders of DashboardNavbar. LanguageMenu doesn't have the same issue, because presumably its state is managed internally, so opening it doesn't cause a re-render of DashboardNavbar.
Sample restructuring of code (not executed, so may have minor errors):
function DesktopNavbar({configMenuState, i18n}) {
return (
<>
<StyledDashboardNavbar>
<Container maxWidth="lg">
<div
style={{
display: "flex",
justifyContent: "flex-end"
}}
>
<Avatar
style={{
backgroundColor:
theme.palette.secondary.main
}}
{...bindTrigger(configMenuState)}
>
{avatarId}
</Avatar>
<DashboardMenu
bindMenu={bindMenu}
menuState={configMenuState}
/>
<LanguageMenu i18n={i18n} />
</div>
</Container>
</StyledDashboardNavbar>
</>
);
}
function MobileNavbar({setDrawer, configDrawer, setConfigDrawer, avatarId}) {
return (
<>
<StyledDashboardNavbar>
<Container maxWidth="md">
<div className="navbar">
<div
style={{
display: "flex",
alignItems: "center"
}}
>
<MenuIcon
color="secondary"
onClick={() => setDrawer(true)}
/>
</div>
<div
className="logo"
onClick={() => setConfigDrawer(true)}
>
<Avatar
style={{
backgroundColor:
theme.palette.secondary.main
}}
>
{avatarId}
</Avatar>
</div>
</div>
</Container>
</StyledDashboardNavbar>
<AvatarDrawer
drawer={configDrawer}
setDrawer={setConfigDrawer}
/>
</>
);
}
export function DashboardNavbar({ setDrawer }) {
// translation hook
const { i18n } = useTranslation("navbar");
// config drawer state
const [configDrawer, setConfigDrawer] = useState(false);
// config menu state
const configMenuState = usePopupState({
variant: "popover",
popupId: "configMenu"
});
// avatar id
const [cookie] = useCookies("userInfo");
const decodedToken = decodeToken(cookie.userInfo.token);
const avatarId =
decodedToken.firstName.charAt(0) + decodedToken.lastName.charAt(0);
return window.innerWidth > 480 ? <DesktopNavbar configMenuState={configMenuState} i18n={i18n} /> : <MobileNavbar setDrawer={setDrawer} configDrawer={configDrawer} setConfigDrawer={setConfigDrawer} avatarId={avatarId} />;
}
An alternative way to fix this is to just eliminate the nested components, so that DashboardNavbar is a single component:
export function DashboardNavbar({ setDrawer }) {
// translation hook
const { i18n } = useTranslation("navbar");
// config drawer state
const [configDrawer, setConfigDrawer] = useState(false);
// config menu state
const configMenuState = usePopupState({
variant: "popover",
popupId: "configMenu"
});
// avatar id
const [cookie] = useCookies("userInfo");
const decodedToken = decodeToken(cookie.userInfo.token);
const avatarId =
decodedToken.firstName.charAt(0) + decodedToken.lastName.charAt(0);
const useDesktopLayout = window.innerWidth > 480;
return <>
{useDesktopLayout &&
<StyledDashboardNavbar>
<Container maxWidth="lg">
<div
style={{
display: "flex",
justifyContent: "flex-end"
}}
>
<Avatar
style={{
backgroundColor:
theme.palette.secondary.main
}}
{...bindTrigger(configMenuState)}
>
{avatarId}
</Avatar>
<DashboardMenu
bindMenu={bindMenu}
menuState={configMenuState}
/>
<LanguageMenu i18n={i18n} />
</div>
</Container>
</StyledDashboardNavbar>
}
{!useDesktopLayout &&
<>
<StyledDashboardNavbar>
<Container maxWidth="md">
<div className="navbar">
<div
style={{
display: "flex",
alignItems: "center"
}}
>
<MenuIcon
color="secondary"
onClick={() => setDrawer(true)}
/>
</div>
<div
className="logo"
onClick={() => setConfigDrawer(true)}
>
<Avatar
style={{
backgroundColor:
theme.palette.secondary.main
}}
>
{avatarId}
</Avatar>
</div>
</div>
</Container>
</StyledDashboardNavbar>
<AvatarDrawer
drawer={configDrawer}
setDrawer={setConfigDrawer}
/>
</>
}
</>;
}
Related answers:
React Material-UI menu anchor broken by react-window list
React/MUI Popover positioning incorrectly with anchorPosition
Material UI Popover is thrown to the top left when used on an inline button in a table with a unique key