Извлечение массива элементов React в функциональный компонент - PullRequest
0 голосов
/ 23 апреля 2020

Я пытаюсь использовать этот компонент, который я написал:

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 типа 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>

<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

Вот стек ошибок:

Check the render method of `ForwardRef(Menu)`.
    in MenuItemList (at ShowItemsDialog.tsx:105)
    in ul (created by ForwardRef(List))
    in ForwardRef(List) (created by WithStyles(ForwardRef(List)))
    in WithStyles(ForwardRef(List)) (created by ForwardRef(MenuList))
    in ForwardRef(MenuList) (created by ForwardRef(Menu))
    in div (created by ForwardRef(Paper))
    in ForwardRef(Paper) (created by WithStyles(ForwardRef(Paper)))
    in WithStyles(ForwardRef(Paper)) (created by Transition)
    in Transition (created by ForwardRef(Grow))
    in ForwardRef(Grow) (created by TrapFocus)
    in TrapFocus (created by ForwardRef(Modal))
    in div (created by ForwardRef(Modal))
    in ForwardRef(Portal) (created by ForwardRef(Modal))
    in ForwardRef(Modal) (created by ForwardRef(Popover))
    in ForwardRef(Popover) (created by WithStyles(ForwardRef(Popover)))
    in WithStyles(ForwardRef(Popover)) (created by ForwardRef(Menu))
    in ForwardRef(Menu) (created by WithStyles(ForwardRef(Menu)))
    in WithStyles(ForwardRef(Menu)) (created by ForwardRef(SelectInput))
    in ForwardRef(SelectInput) (created by ForwardRef(InputBase))
    in div (created by ForwardRef(InputBase))
    in ForwardRef(InputBase) (created by WithStyles(ForwardRef(InputBase)))
    in WithStyles(ForwardRef(InputBase)) (created by ForwardRef(FilledInput))
    in ForwardRef(FilledInput) (created by WithStyles(ForwardRef(FilledInput)))
    in WithStyles(ForwardRef(FilledInput)) (created by ForwardRef(Select))
    in ForwardRef(Select) (created by WithStyles(ForwardRef(Select)))
    in WithStyles(ForwardRef(Select)) (created by ForwardRef(TextField))
    in div (created by ForwardRef(FormControl))
    in ForwardRef(FormControl) (created by WithStyles(ForwardRef(FormControl)))
    in WithStyles(ForwardRef(FormControl)) (created by ForwardRef(TextField))
    in ForwardRef(TextField) (created by WithStyles(ForwardRef(TextField)))
    in WithStyles(ForwardRef(TextField)) (created by FormikMaterialUITextField)
    in FormikMaterialUITextField (created by Field)
    in Field (at ShowItemsDialog.tsx:77)

Ответы [ 2 ]

1 голос
/ 23 апреля 2020

Объяснение 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;
1 голос
/ 23 апреля 2020

Кажется, проблема не в этом компоненте. Не могли бы вы вставить код компонента, где вы используете MenuItemList? Кажется, проблема с ref response , используемым Material-ui

. Я также рекомендую использовать 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, который используется внутри меню в меню. «Содержание якорь» является DOM-элемент в меню, что попытки поповера выстраиваться с якорем элементом (элемент вне меню, которое является точкой отсчета для позиционирования меню).

Для того, чтобы рычаги Первый дочерний элемент как якорь контента, Menu добавляет ссылку на него (используя cloneElement). Чтобы не получить полученную ошибку (и чтобы позиционирование работало правильно), вашему функциональному компоненту необходимо переслать ссылку на один из компонентов, которые он отображает (обычно это самый внешний компонент - 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;
...