javascript - React render siblings on Multiple Select Material UI - Stack Overflow

This is my problem:I need to create a multiple select (with checkboxes) on React using material UI, ju

This is my problem: I need to create a multiple select (with checkboxes) on React using material UI, just like the following image:

I have the data in an array like the following

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 }
    ]
  }
]

Perfect. Everything seems to have to do 2 nested maps, nothing out of the ordinary. Meanwhile, I try this:

<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>

That renders the select well, makes it look nice. But my problem is that (apparently) material doesn't accept React.Fragment (<></>) inside select. Meanwhile, I can't generate the structure that Material UI specifies in the documentation looping through an array:

<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>

If i put a <div> or other element instead of <></>, the onchange event stops working.

I also tried to use rendering with arrays, but did not have good results since the second sibling is not a jsx, but I have to loop through the array.

{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>
    ))}
  ])}

I am blocked, I do not know how it can be solved. I thought of a child ponent but I am going to have the same problem when rendering it.

Is there a way to solve it?

I leave a link with the example snippet:

Thank you very much.

This is my problem: I need to create a multiple select (with checkboxes) on React using material UI, just like the following image:

I have the data in an array like the following

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 }
    ]
  }
]

Perfect. Everything seems to have to do 2 nested maps, nothing out of the ordinary. Meanwhile, I try this:

<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>

That renders the select well, makes it look nice. But my problem is that (apparently) material doesn't accept React.Fragment (<></>) inside select. Meanwhile, I can't generate the structure that Material UI specifies in the documentation looping through an array:

<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>

If i put a <div> or other element instead of <></>, the onchange event stops working.

I also tried to use rendering with arrays, but did not have good results since the second sibling is not a jsx, but I have to loop through the array.

{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>
    ))}
  ])}

I am blocked, I do not know how it can be solved. I thought of a child ponent but I am going to have the same problem when rendering it.

Is there a way to solve it?

I leave a link with the example snippet: https://codesandbox.io/s/material-demo-h77i8

Thank you very much.

Share Improve this question asked Jun 3, 2020 at 5:13 Federico SaenzFederico Saenz 5321 gold badge6 silver badges21 bronze badges
Add a ment  | 

4 Answers 4

Reset to default 6 +50

You could check this codesandbox here.

What you can note from this answer is that you can always create elements without the need of fragments. As fragments are just the wrappers around array. Since, material doesn't want fragments in them. You could use pure array to render your elements instead of wrapping it into fragments.


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;
}

You can look how I used another function to push into javascript array and then render them to the tree.

I went through your code and had made some changes to it. You can find it here: https://codesandbox.io/s/material-demo-70svb

Although it's now fully working as you have to define state with useState and then persist your values.I have added console in eventHandlers to give you some insights of what you can do to resolve your problem

There are three different aspects that I have changed:

  • Rather than using a fragment, add the menu items into an array (menuItems in the example below) and then render the array (same idea as Dipesh Dulal's answer).
  • Improve the behavior of the checkboxes. In your original code, nothing was keeping the checkbox state in sync with whether the Select considered the menu item to be selected. I have wrapped the checkbox into a custom MenuItemWithCheckbox ponent which uses the selected prop (which is injected by Select) to control the checked state of the checkbox.
  • Fix the look of the selected value. The default display of the selected value (when not using Select's renderValue prop) uses the children of the menu item as the display text. This means that if the MenuItem's children contains something other than text, it will display strangely. By introducing the custom MenuItemWithCheckbox ponent to handle the checkbox display, the direct children now contains only text and the default display works reasonably.

Here is the code from my modified version of your sandbox:

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

I've created an example which is similar to your code. You can try it in codesandbox here. Hopefully, it can help you.
This is my code:

...
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>
  );
}

发布者:admin,转转请注明出处:http://www.yc00.com/questions/1744743146a4591147.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信