bdemirka
bdemirka

Reputation: 837

React How to render nested dropdown list in a form from a json data?

I have a form that I used material ui and formik to implement it. Inside, I need to have a selection box, with nested options, it should look like this eventually. (Then I should get the value selected and submit form ) enter image description here

For simplicity: I only want recursive children items rendering from a json file:

enter image description here

I have a json file that is nested with children objects. Sample:

[
    {
        "label": "Bavullar ve \u00c7antalar",
        "value": 5181.0,
        "children": [
            {
                "label": "Al\u0131\u015fveri\u015f \u00c7antalar\u0131",
                "value": 5608.0
            },
            {
                "label": "Bavul Aksesuarlar\u0131",
                "value": 110.0,
                "children": [
                    {
                        "label": "Korumal\u0131 Kap D\u00fczenleyicileri ve B\u00f6lme Ekleri",
                        "value": 503014.0
                    },
                    {
                        "label": "Seyahat Keseleri",
                        "value": 5650.0
                    },
                    {
                        "label": "Seyahat \u015ei\u015fe ve Kaplar\u0131",
                        "value": 6919.0
                    }
                ]
            },
            {
                "label": "Bebek Bezi \u00c7antalar\u0131",
                "value": 549.0
            },
            {
                "label": "Bel \u00c7antalar\u0131",
                "value": 104.0
            },
            {
                "label": "Elbise \u00c7antalar\u0131",
                "value": 105.0
            }
...

This is the form I need to insert my dropdown.

// import categories from json file

     import categories from '../gpc.tr.json'

 // Form component
<form onSubmit={formik.handleSubmit} className={classes.root} id='form'>
      <FormhelperText> <span style={{color:'red'}}>*</span> Set Google Product Category ID to </FormhelperText>
      <Select id="category" label="Set Google Product Category ID to" fullWidth variant="outlined" name="schema.category"
       value={formik.values.schema.category}
       onChange={formik.handleChange}
       error={formik.touched?.schema?.category && Boolean(formik.errors.category)}
       helpertext={formik.touched?.schema?.category && formik.errors.category}>
        // HERE I TRY TO INSERT A COMPONENT FOR DROPDOWN
        {renderCategories(categories)}
       </Select>

so in renderCategories component, I use recursion to render nested children. But I couldn't make it work.

const renderCategories = (categories) => {
  return (<div>
    {categories.map(i => {
      return <MenuItem key={i.value} value={i.value}>
        { i.label } 
       { i.children && renderCategories(i.children) } 
        </MenuItem>
    })}
    </div>
  )}

I get <li> cannot appear as a descendant of <li>. warning and all of the children elements appear all at once. Like in the picture. Even if I style it and hide children elements, I feel like there is a problem here, it gets really slow to render it. How should I solve this problem? Is there a better way to implement it? enter image description here

Here is the sandbox link: https://codesandbox.io/s/empty-moon-4diw4?file=/src/ProductForm.js

UPDATE

I used NestedMenuItem from "material-ui-nested-menu-item" package. But still, my recursive function doesn't work right.

 function renderCategories (categories) {
     return categories.map((i) => {
        return(
        <NestedMenuItem key={i.value} value={i.value}
        label={i.label}
        parentMenuOpen={!!menuPosition}
        onClick={handleItemClick}>
               <MenuItem
              component={'div'}
              onClick={handleItemClick}
              key={i.value}> { i.label }
               </MenuItem>
         { i.children && renderCategories(i.children) } 
          </NestedMenuItem>
        )
      })
   }

I get lost here, it doesn't render it properly. how would you implement a recursive dropdown list inside a form in react?

Upvotes: 3

Views: 7949

Answers (3)

mr_nocoding
mr_nocoding

Reputation: 487

I would use ant design cascader for such a big data set. https://ant.design/components/cascader/ There are different components you can use and you don't need a recursion just pass your json as options. Sample usage:

import { Cascader } from 'antd';

const options = [
  {
    value: 'zhejiang',
    label: 'Zhejiang',
    children: [
      {
        value: 'hangzhou',
        label: 'Hangzhou',
        children: [
          {
            value: 'xihu',
            label: 'West Lake',
          },
        ],
      },
    ],
  },
  {
    value: 'jiangsu',
    label: 'Jiangsu',
    children: [
      {
        value: 'nanjing',
        label: 'Nanjing',
        children: [
          {
            value: 'zhonghuamen',
            label: 'Zhong Hua Men',
          },
        ],
      },
    ],
  },
];

function onChange(value) {
  console.log(value);
}

ReactDOM.render(
  <Cascader options={options} onChange={onChange} placeholder="Please select" />,
  mountNode,
);

Upvotes: 1

alisasani
alisasani

Reputation: 2968

If you pass component="div" to the MenuItem component, the error that you faced would be removed. sandbox

<MenuItem
              onClick={() => console.log(123)}
              component="div"
              key={i.value}
              value={i.value}
            >

Upvotes: 0

Nick Rameau
Nick Rameau

Reputation: 1318

If you want to do nested lists, MenuItem is not the right component for this.

It's using the li tag as a base and this is why you're getting this warning.

I suggest you use an external package for this, material-ui-nested-menu-item, created exactly for this.

All you have to do is to replace MenuItem by its default NestedMenuItem component, wrap them with a Menu container and use the container ref:

<Menu
  open={!!menuPosition}
  onClose={() => setMenuPosition(null)}
  anchorReference="anchorPosition"
  anchorPosition={menuPosition}
>
  <MenuItem onClick={handleItemClick}>Button 1</MenuItem>
  <MenuItem onClick={handleItemClick}>Button 2</MenuItem>
  <NestedMenuItem
    label="Button 3"
    parentMenuOpen={!!menuPosition}
    onClick={handleItemClick}
  >
    <MenuItem onClick={handleItemClick}>Sub-Button 1</MenuItem>
    <MenuItem onClick={handleItemClick}>Sub-Button 2</MenuItem>
    <NestedMenuItem
      label="Sub-Button 3"
      parentMenuOpen={!!menuPosition}
      onClick={handleItemClick}
    >
      <MenuItem onClick={handleItemClick}>Sub-Sub-Button 1</MenuItem>
      <MenuItem onClick={handleItemClick}>Sub-Sub-Button 2</MenuItem>
    </NestedMenuItem>
  </NestedMenuItem>
  <MenuItem onClick={handleItemClick}>Button 4</MenuItem>
  <NestedMenuItem
    label="Button 5"
    parentMenuOpen={!!menuPosition}
    onClick={handleItemClick}
  >
    <MenuItem onClick={handleItemClick}>Sub-Button 1</MenuItem>
    <MenuItem onClick={handleItemClick}>Sub-Button 2</MenuItem>
  </NestedMenuItem>
</Menu>

Click here to see a CodeSandbox of its usage.

Another solution would be to make MenuItem use a different tag as base by providing the component prop.

Upvotes: 1

Related Questions