在多个选择材质UI上反应渲染同级

时间:2020-06-03 05:13:19

标签: javascript reactjs material-ui

这是我的问题: 我需要使用材质UI在React上创建一个多选(带有复选框),如下图所示:

React multiple select with hierarchy Country/states

我将数据存储在如下所示的数组中

data = [
  {
    id: 199,
    name: "Argentina",
    state: [
      { name: "Buenos Aires", id: 1 },
      { name: "Cordoba", id: 2 },
      { name: "Chaco", id: 2 }
    ]
  },
  {
    id: 200,
    name: "USA",
    state: [
      { name: "California", id: 4 },
      { name: "Florida", id: 5 },
      { name: "Texas", id: 6 }
    ]
  }
]

完美。一切似乎都必须做2个嵌套地图,这与众不同。同时,我尝试这样做:

<InputLabel htmlFor="grouped-select">Grouping</InputLabel>
<Select
  defaultValue={[]}
  id="grouped-select"
  onChange={handleChange}
  multiple
>
  <MenuItem value="">
    <Checkbox />
    <em>All</em>
  </MenuItem>
  {data.map(country => (
    <>
      <ListSubheader key={country.id}>{country.name}</ListSubheader>
      {country.state.map(state => (
        <MenuItem key={state.id} value={state.id}>
          <Checkbox />
          {state.name}
        </MenuItem>
      ))}
    </>
  ))}
</Select>

这使选择效果很好,使其看起来不错。但是我的问题是(显然)材料在select内部不接受React.Fragment(<></>)。同时,我无法生成Material UI在通过数组循环的文档中指定的结构:

<ListSubheader>Parent 1</ListSubHeader>
<MenuItem key={state.id} value={state.id}>Child1</MenuItem>
<MenuItem key={state.id} value={state.id}>Child2</MenuItem>
<MenuItem key={state.id} value={state.id}>Child...n</MenuItem>

<ListSubheader>Parent 2</ListSubHeader>
<MenuItem key={state.id} value={state.id}>Child</MenuItem>
<MenuItem key={state.id} value={state.id}>Child...n</MenuItem>

如果我放置<div>或其他元素而不是<></>,则 onchange 事件将停止工作。

我还尝试对数组使用渲染,但是由于第二个兄弟姐妹不是jsx,所以效果不佳,但是我必须遍历数组。

{data.map(country => [
    <ListSubheader key={country.id}>{country.name}</ListSubheader>
    {country.state.map(state => (
      <MenuItem key={state.id} value={state.id}>
        <Checkbox />
        {state.name}
      </MenuItem>
    ))}
  ])}

我被封锁了,我不知道如何解决。我想到了一个子组件,但是在渲染它时会遇到同样的问题。

有办法解决吗?

我留下了带有示例片段的链接:https://codesandbox.io/s/material-demo-h77i8

非常感谢您。

4 个答案:

答案 0 :(得分:3)

您可以选中此代码框here

您可以从该答案中注意到的是,您始终可以创建元素而无需片段。由于片段只是数组的包装。因为,材料不需要它们中的碎片。您可以使用纯数组来呈现元素,而不必将其包装成碎片。


function makeItems(data) {
  const items = [];
  for (let country of data) {
    items.push(<ListSubheader key={country.id}>{country.name}</ListSubheader>);
    for (let state of country.state) {
      items.push(
        <MenuItem key={state.id} value={state.id}>
          <Checkbox />
          {state.name}
        </MenuItem>
      );
    }
  }
  return items;
}

您可以看看我如何使用另一个函数将其推入javascript数组,然后将其渲染到树中。

答案 1 :(得分:0)

我遍历了您的代码并对它进行了一些更改。您可以在这里找到它:https://codesandbox.io/s/material-demo-70svb

尽管它现在已经可以正常工作了,因为您必须使用useState定义状态然后保留您的值。我在eventHandlers中添加了控制台,以使您对可以解决问题的方法有一些了解

答案 2 :(得分:0)

我改变了三个方面:

  • 而不是使用片段,而是将菜单项添加到一个数组(在下面的示例中为menuItems),然后渲染该数组(与Dipesh Dulal的答案相同)。
  • 改善复选框的行为。在您的原始代码中,没有使复选框状态与Select是否考虑要选择的菜单项保持同步。我已经将复选框包装到自定义的MenuItemWithCheckbox组件中,该组件使用selected属性(即injected by Select)来控制复选框的checked状态。
  • 修复所选值的外观。所选值的默认显示(当不使用Select的renderValue道具时)使用children of the menu item作为显示文本。这意味着,如果MenuItem的子级包含文本以外的其他内容,它将显示异常。通过引入自定义MenuItemWithCheckbox组件来处理复选框显示,直接子代现在仅包含文本,并且默认显示可以正常工作。

这是我修改后的沙箱中的代码:

import React from "react";
import { makeStyles } from "@material-ui/core/styles";
import InputLabel from "@material-ui/core/InputLabel";
import MenuItem from "@material-ui/core/MenuItem";
import ListSubheader from "@material-ui/core/ListSubheader";
import FormControl from "@material-ui/core/FormControl";
import Select from "@material-ui/core/Select";
import { Checkbox } from "@material-ui/core";

const useStyles = makeStyles(theme => ({
  formControl: {
    margin: theme.spacing(1),
    minWidth: 120
  }
}));

const data = [
  {
    id: 199,
    name: "Argentina",
    state: [
      { name: "Buenos Aires", id: 1 },
      { name: "Cordoba", id: 2 },
      { name: "Chaco", id: 3 }
    ]
  },
  {
    id: 200,
    name: "USA",
    state: [
      { name: "California", id: 4 },
      { name: "Florida", id: 5 },
      { name: "Texas", id: 6 }
    ]
  }
];

const handleChange = ev => {
  //console.log(ev);
};

const MenuItemWithCheckbox = React.forwardRef(function MenuItemWithCheckbox(
  { children, selected, em = false, ...other },
  ref
) {
  return (
    <MenuItem {...other} selected={selected} ref={ref}>
      <Checkbox checked={selected} />
      {em && <em>{children}</em>}
      {!em && children}
    </MenuItem>
  );
});

export default function GroupedSelect() {
  const classes = useStyles();
  const menuItems = [];
  menuItems.push(
    <MenuItemWithCheckbox key="" value="" em={true}>
      All
    </MenuItemWithCheckbox>
  );
  data.forEach(country => {
    menuItems.push(
      <ListSubheader key={country.id}>{country.name}</ListSubheader>
    );
    country.state.forEach(state => {
      menuItems.push(
        <MenuItemWithCheckbox key={state.id} value={state.id}>
          {state.name}
        </MenuItemWithCheckbox>
      );
    });
  });
  return (
    <div>
      <FormControl className={classes.formControl}>
        <InputLabel htmlFor="grouped-select">Grouping</InputLabel>
        <Select
          defaultValue={[]}
          id="grouped-select"
          onChange={handleChange}
          multiple
        >
          {menuItems}
        </Select>
      </FormControl>
    </div>
  );
}

Edit Select with checkboxes and sub headers

答案 3 :(得分:-2)

我创建了一个与您的代码相似的示例。您可以在codeandbox here中尝试。希望可以为您提供帮助。
这是我的代码:

...
const names = [
  "Oliver Hansen",
  "Van Henry",
  "April Tucker",
  "Ralph Hubbard",
  "Omar Alexander",
  "Carlos Abbott",
  "Miriam Wagner",
  "Bradley Wilkerson",
  "Virginia Andrews",
  "Kelly Snyder"
];

export default function MultipleSelect() {
  const classes = useStyles();
  const [personName, setPersonName] = React.useState([]);

  const handleChangeMultiple = event => {
    const { value } = event.target;
    const allIndex = value.indexOf("All");
    if (allIndex > -1) {
      const values = ["All"];
      for (let name of names) {
        values.push(name);
      }
      setPersonName(values);
    } else {
      setPersonName([]);
    }
  };

  return (
    <div>
      <FormControl className={classes.formControl}>
        <InputLabel id="demo-mutiple-checkbox-label">Tag</InputLabel>
        <Select
          labelId="demo-mutiple-checkbox-label"
          id="demo-mutiple-checkbox"
          multiple
          value={personName}
          onChange={handleChangeMultiple}
          input={<Input />}
          renderValue={selected => selected.join(", ")}
          MenuProps={MenuProps}
        >
          <MenuItem key="All" value="All">
            <Checkbox checked={personName.indexOf("All") > -1} />
            <em>All</em>
          </MenuItem>
          {names.map(name => (
            <MenuItem key={name} value={name}>
              <Checkbox checked={personName.indexOf(name) > -1} />
              <ListItemText primary={name} />
            </MenuItem>
          ))}
        </Select>
      </FormControl>
    </div>
  );
}