Reputation: 833
I have a function that can create menu on their own , and I want to use popover on those menu like below.
const popInitialPos = {
mouseX: null,
mouseY: null,
};
const [popState, setPopState] = React.useState<{
mouseX: null | number;
mouseY: null | number;
}>(popInitialPos);
const openPopCategory = (event: React.MouseEvent<HTMLDivElement>) => {
event.preventDefault();
setPopState({
mouseX: event.clientX - 2,
mouseY: event.clientY - 4,
});
};
const closePopCategory = () => {
setPopState(popInitialPos);
};
//....some code
{categories?.map( (c)=>{
if( c.id>=5){
return(
<Link to={{pathname:"/list/"+c.id}} className={classes.linkItem} key={c.id}>
<div className={classes.item} onContextMenu={(e)=>openPopCategory(e)}>
<Dns className={classes.icon} />
<Typography className={classes.text}>{c.name}</Typography>
<Typography className={classes.text} style={{marginLeft:"auto"}}>
{todos.filter((todo)=>todo.categories?.includes(c.id)&&todo.completed===false).length ===0 ? null : todos.filter((todo)=>todo.categories?.includes(c.id)&&todo.completed===false).length }
</Typography>
<Menu
keepMounted
open={popState.mouseY !== null}
onClose={closePopCategory}
anchorReference="anchorPosition"
anchorPosition={
popState.mouseY !== null && popState.mouseX !== null
? { top: popState.mouseY, left: popState.mouseX }
: undefined
}
>
<MenuItem onClick={closePopCategory}>{c.name}</MenuItem>
</Menu>
</div>
</Link>
)
}
}
)}
So after I did that , when I rightclick the components the menuItem between different list will overlap each other, I think that's because I used the same state to control menu, but I can't think of a way to create multiple state or function with mapping , and I don't think that's a great way either. What is the normal approach if you want to add popover to mapping components since it's control by state and function ?
Upvotes: 0
Views: 1167
Reputation: 5497
We can create a separate component for the MenuWithPopup and have the state reside inside it . This way of creating atomic state
ensures that
we have a separate state for each menu item .
const popInitialPos = {
mouseX: null,
mouseY: null,
};
const MenuWithPopup = () => {
const [popState, setPopState] = React.useState<{
mouseX: null | number;
mouseY: null | number;
}>(popInitialPos);
const openPopCategory = (event: React.MouseEvent<HTMLDivElement>) => {
event.preventDefault();
setPopState({
mouseX: event.clientX - 2,
mouseY: event.clientY - 4,
});
};
const closePopCategory = () => {
setPopState(popInitialPos);
};
return (
<Link
to={{pathname: '/list/' + c.id}}
className={classes.linkItem}
key={c.id}
>
<div className={classes.item} onContextMenu={(e) => openPopCategory(e)}>
<Dns className={classes.icon} />
<Typography className={classes.text}>{c.name}</Typography>
<Typography className={classes.text} style={{marginLeft: 'auto'}}>
{todos.filter(
(todo) =>
todo.categories?.includes(c.id) && todo.completed === false
).length === 0
? null
: todos.filter(
(todo) =>
todo.categories?.includes(c.id) && todo.completed === false
).length}
</Typography>
<Menu
keepMounted
open={popState.mouseY !== null}
onClose={closePopCategory}
anchorReference="anchorPosition"
anchorPosition={
popState.mouseY !== null && popState.mouseX !== null
? {top: popState.mouseY, left: popState.mouseX}
: undefined
}
>
<MenuItem onClick={closePopCategory}>{c.name}</MenuItem>
</Menu>
</div>
</Link>
);
};
Now we can use this component inside the map
{
categories?.map((c) => {
if (c.id >= 5) {
return <MenuItemWithPopup {pass the necessary props here} />;
}
});
}
Upvotes: 1
Reputation: 7101
If you need additional state per each "mapped" react node, the easiest way is to put the whole node into its own component. So you would do something along the lines of:
categories?.map(c => <Category key={c.id} category={c} />)
And then in your new component handle the state:
function Category({ category }) {
const [state, setState] = useState(...)
return <Link>
<Menu />
... etc
</Link>
}
Upvotes: 1