将元素的React数组提取到功能组件中

时间:2020-04-23 07:32:15

标签: reactjs material-ui react-forwardref

我正在尝试使用我编写的该组件:

import React from 'react';
import MenuItem from '@material-ui/core/MenuItem';
import ListItemText from '@material-ui/core/ListItemText';

const ITEMS = {
  item1: { id: 1, name: '1', description: 'item1', protected: true },
  item2: { id: 2, name: '2', description: 'item2' },
  item3: { id: 3, name: '3', description: 'item3' },
}

// See https://github.com/DefinitelyTyped/DefinitelyTyped/issues/20356#issuecomment-435708501
// for an explanation on why the component return type was cast to `any` below.
const MenuItemList: any = () => {
  return Object.values(ITEMS).map(
    (item) =>
      !item.protected && (
        <MenuItem key={item.id} value={item.id}>
          <ListItemText
            primary={item.name}
            secondary={item.description}
          />
        </MenuItem>
      )
  );
};

export default MenuItemList;

...一次在类型Textfield的{​​{1}}内,另一次在select组件内。但是,在浏览器中访问它时出现以下错误:

Menu

关于如何解决此问题的任何想法?

更新:

这是我称呼此组件的方式:

Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?

<Menu
  id={id}
  open={open}
  anchorEl={anchorEl}
  getContentAnchorEl={null}
  keepMounted={false}
  onClose={handleClose}
  elevation={2}
  PaperProps={{
      square: true,
  }}
  anchorOrigin={{
      vertical: 'bottom',
      horizontal: 'right',
  }}
  transformOrigin={{
      vertical: 'top',
      horizontal: 'right',
  }}
>
  < MenuItemList />
</Menu>

更新2

这是错误堆栈:

<Field
  name="items"
  label="Select Item"
  padding={2}
  component={TextField}
  select
  fullWidth
  SelectProps={{
    MenuProps: {
      elevation: 2,
      getContentAnchorEl: null,
      anchorOrigin: {
        vertical: 'bottom',
        horizontal: 'left',
      },
      transformOrigin: {
        vertical: 'top',
        horizontal: 'left',
      },
    },
    IconComponent: ExpandMoreIcon,
  }}
  variant="filled"
  InputProps={{
    disableUnderline: true,
  }}
>
  <MenuItemList />
</Field>

2 个答案:

答案 0 :(得分:1)

似乎问题不在此组件上。您可以在使用MenuItemList的地方粘贴组件的代码吗?在Material-ui使用的ref react上似乎是个问题

我还建议使用filter [doc]来过滤受保护的项目。

import React from 'react';
import MenuItem from '@material-ui/core/MenuItem';
import ListItemText from '@material-ui/core/ListItemText';

const ITEMS = {
  item1: { id: 1, name: '1', description: 'item1', protected: true },
  item2: { id: 2, name: '2', description: 'item2' },
  item3: { id: 3, name: '3', description: 'item3' },
}

const MenuItemList: any = () => {
  return Object.values(ITEMS).filter(item => !item.protected).map(
    (item) =>
        <MenuItem key={item.id} value={item.id}>
          <ListItemText
            primary={item.name}
            secondary={item.description}
          />
        </MenuItem>
  );
};

export default MenuItemList;

修改

菜单使用菜单的第一个子项作为菜单内部使用的Popover组件的“内容锚”。 “内容锚点”是Popover尝试与锚点元素(菜单外部的元素,该元素是用于放置菜单的参考点)对齐的菜单中的DOM元素。

为了利用第一个孩子作为内容锚点,Menu向其添加了一个引用(使用cloneElement)。为了避免收到您收到的错误(并使定位正常工作),功能组件需要将ref转发到它提供的组件之一(通常是最外面的组件-在您的情况下为div)。

将div用作Menu的直接子代时,不会出现错误,因为div可以成功接收到引用。

因此,您应该将MenuItemList的代码更改为:

import React from 'react';
import MenuItem from '@material-ui/core/MenuItem';
import ListItemText from '@material-ui/core/ListItemText';

const ITEMS = {
  item1: { id: 1, name: '1', description: 'item1', protected: true },
  item2: { id: 2, name: '2', description: 'item2' },
  item3: { id: 3, name: '3', description: 'item3' },
}

const MenuItemList: any = () => {
  return (
    <div>
      {Object.values(ITEMS)
        .filter(item => !item.protected)
        .map(item => (
          <MenuItem key={item.id} value={item.id}>
            <ListItemText primary={item.name} secondary={item.description} />
          </MenuItem>
        ))}
    </div>
  );
};

export default MenuItemList;

答案 1 :(得分:1)

stefano.orlando的解释似乎是正确的,直到他们提出解决方案为止。将div包裹在其他组件周围不会导致引用被转发。为此,您需要使用React.forwardRef。以下代码将使之成为参考,如果将引用传递到MenuItemList,则该引用将被重定向到MenuItem内部的第一个MenuItemList

import React from 'react';
import MenuItem from '@material-ui/core/MenuItem';
import ListItemText from '@material-ui/core/ListItemText';

const ITEMS = {
  item1: { id: 1, name: '1', description: 'item1', protected: true },
  item2: { id: 2, name: '2', description: 'item2' },
  item3: { id: 3, name: '3', description: 'item3' },
}

const MenuItemList: any = React.ForwardRef((props, ref) => {
  return (
    Object.values(ITEMS)
      .filter(item => !item.protected)
      .map((item, index) => (
        <MenuItem 
          ref={index === 0 ? ref : undefined}
          key={item.id} 
          value={item.id}
        >
          <ListItemText primary={item.name} secondary={item.description} />
        </MenuItem>
      ))
  );
});

export default MenuItemList;