【问题标题】:How to handle multiple menu state with Material-UI Menu component?如何使用 Material-UI Menu 组件处理多个菜单状态?
【发布时间】:2021-05-12 20:53:40
【问题描述】:

我有一组动态生成的下拉菜单和手风琴,它们填充在渲染客户端(经过验证的用户从 db 购买)。

我遇到了一个错误,我确信来自我的菜单 anchorEl 不知道使用 anchorEl 打开“哪个”菜单。 MUI 文档并没有真正涵盖多个动态菜单,所以我不确定如何管理打开的菜单

这是说明我的用例的图片:

如您所见,锚定的菜单实际上是最后渲染的元素。每个下载按钮都显示最后呈现的菜单。我已经进行了研究,我认为我已经将其缩减为 anchorEl 并打开道具。

这是我的代码。请记住,数据结构按预期工作,所以我省略了它以保持简短,并且因为它来自 firebase,我必须在这里完全重新创建它(我认为它是多余的)。

组件:

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

我认为还值得一提的是,我确实检查了呈现的 HTML,并且正确的列表按顺序排列在那里 - 它只是假设状态的最后一个。提前致谢,如果我遗漏了什么,或者我能以任何方式澄清,请告诉我。 :)

【问题讨论】:

    标签: javascript reactjs material-ui next.js react-state-management


    【解决方案1】:

    我无法让菜单动态化, 相反,我使用了折叠面板示例,并在数组的每个项目上使用属性 isOpen 进行操作。

    Check Cards Collapse Example 在 setIsOpen 方法上,您可以更改此 bool 属性:

      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>
    

    【讨论】:

    • 我忘了这个。我修复它的方法是让单个状态 obj 初始化为 null,然后我将所有折叠面板包装成这样:{ Boolean(stateObj === id)}。我就是这样做的。
    猜你喜欢
    • 2018-09-20
    • 1970-01-01
    • 1970-01-01
    • 2022-12-18
    • 1970-01-01
    • 2020-06-19
    • 1970-01-01
    • 2013-08-16
    • 1970-01-01
    相关资源
    最近更新 更多