首页 > 解决方案 > MUI 选择自定义 MenuItem 无法正常工作

问题描述

在将 MUI 与单独的组件MenuItem结合并将其呈现时,我遇到了一个问题。Select

这是代码框

基本上,我有这样的事情:

import { Select } from "@material-ui/core";
import CustomMenuItem from "./CustomMenuItem";
import React from "react";

export default function App() {
  const userIds = [1, 2, 3];
  return (
    <Select
      id="user"
      name="User"
      onChange={(event: React.ChangeEvent<{ value: unknown }>) => {
        alert(event.target.value as number);
      }}
    >
      {userIds.map((userId) => (
        <CustomMenuItem key={userId} userId={userId} />
      ))}
    </Select>
  );
}

这是自定义项目:

import { MenuItem, Typography } from "@material-ui/core";
import React from "react";

interface CustomMenuItemProps {
  userId: number;
}

const CustomMenuItem = React.forwardRef<HTMLLIElement, CustomMenuItemProps>(
  (props: CustomMenuItemProps, ref) => {
    const { userId, ...rest } = props;
    return (
      <MenuItem value={userId} {...rest} ref={ref}>
        <Typography>{userId}</Typography>
      </MenuItem>
    );
  }
);
export default CustomMenuItem;

起初,我在没有任何引用的情况下完成了此操作,但这在控制台 ( Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?) 中给了我一个错误,所以在谷歌搜索了一段时间后,我发现我必须通过这个引用。我也传递了...rest道具,因为我知道MenuItem需要它们。

预期行为:当我单击 时MenuItem,它会在Select组件中被选中。

实际行为:没有任何反应。

问题是,我制作了CustomMenuItem它以使其可重复使用。但在此之前,我有一个简单的函数,比如:renderItem我在 inSelect.renderValue和 in 中都使用userIds.map过它,它的代码与CustomMenuItem- 它返回相同的 JSX 树。当时它起作用了,但由于某种原因,它现在不起作用。所以,如果我会这样做:

    <Select
      id="user"
      name="User"
      onChange={(event: React.ChangeEvent<{ value: unknown }>) => {
        alert(event.target.value as number);
      }}
    >
      {userIds.map((userId) => (
        <MenuItem key={userId} value={userId}>
          <Typography>{userId}</Typography>
        </MenuItem>
      ))}
    </Select>

它只是工作:(

我在这里错过了什么吗?

标签: reactjstypescriptmaterial-uireact-forwardref

解决方案


有一些实现细节Select阻碍了尝试以MenuItem这种方式进行自定义。

Select 使用其直系孩子的价值道具Select在你的情况下,直接的孩子是CustomMenuItem只有一个userId道具的元素——没有value道具;因此,当您单击其中一个自定义菜单项时,会Select找到新值。undefined

您可以通过将userId道具复制为value道具来解决此问题:

import { Select } from "@material-ui/core";
import CustomMenuItem from "./CustomMenuItem";
import React from "react";

export default function App() {
  const userIds = [1, 2, 3];
  const [value, setValue] = React.useState(1);
  console.log("value", value);
  return (
    <Select
      id="user"
      name="User"
      value={value}
      onChange={(event: React.ChangeEvent<{ value: unknown }>) => {
        setValue(event.target.value as number);
      }}
    >
      {userIds.map((userId) => (
        <CustomMenuItem key={userId} value={userId} userId={userId} />
      ))}
    </Select>
  );
}

编辑 MUI 选择自定义 MenuItem

Select如果您查看控制台日志,这将成功更改 的值。由于另一个问题,我稍后会解释新值未成功显示。

您可能会想“那么我可以只使用valueprop 而不是userIdprop 而不是两者都使用”,但valueprop 实际上不会到达您的自定义组件。Select用于React.cloneElementvalue prop 更改为 undefined,而是将其放入data-value以避免value在最终 html 中指定 prop (这对于呈现的 html 元素不是有效属性)。

在我上面的沙箱中,您会注意到,当您选择一个值时,新值没有成功显示为所选值。这是因为除非您指定renderValue 属性,否则Select 使用所选子项的 children 属性作为显示值。元素的prop未定义。childrenCustomMenuItem

您可以通过使用上的renderValue道具SelectuserId再次将其指定为子项来解决此问题:

import { Select } from "@material-ui/core";
import CustomMenuItem from "./CustomMenuItem";
import React from "react";

export default function App() {
  const userIds = [1, 2, 3];
  const [value, setValue] = React.useState(1);
  console.log("value", value);
  return (
    <Select
      id="user"
      name="User"
      value={value}
      onChange={(event: React.ChangeEvent<{ value: unknown }>) => {
        setValue(event.target.value as number);
      }}
    >
      {userIds.map((userId) => (
        <CustomMenuItem key={userId} value={userId} userId={userId}>
          {userId}
        </CustomMenuItem>
      ))}
    </Select>
  );
}

编辑 MUI 选择自定义 MenuItem(分叉)

这有效,但也删除了自定义菜单项组件试图提供的所有值。我认为实现这一点的最简单方法(同时仍然适用于 Material-UISelect设计)是将可重用代码放入用于呈现菜单项的函数中,而不是制作自定义菜单项组件:

import { Select } from "@material-ui/core";
import React from "react";
import { MenuItem, Typography } from "@material-ui/core";

const renderMenuItem = (value: number) => {
  return (
    <MenuItem key={value} value={value}>
      <Typography>{value}</Typography>
    </MenuItem>
  );
};

export default function App() {
  const userIds = [1, 2, 3];
  const [value, setValue] = React.useState(1);
  console.log("value", value);
  return (
    <Select
      id="user"
      name="User"
      value={value}
      onChange={(event: React.ChangeEvent<{ value: unknown }>) => {
        setValue(event.target.value as number);
      }}
    >
      {userIds.map(renderMenuItem)}
    </Select>
  );
}

编辑 MUI 选择自定义 MenuItem


推荐阅读