Kai021195
Kai021195

Reputation: 833

How to use menu or popover on mapping components in React?

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

Answers (2)

Shyam
Shyam

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

torvin
torvin

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

Related Questions