3

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?

4

3 Answers 3

1
+50

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,
);
Sign up to request clarification or add additional context in comments.

Comments

1

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.

4 Comments

@alisasani Let's hear from OP to see if that doesn't fix it.
sorry if it bothered you. I just said to ask whether it solves or not.
I see this package but unfortunately, I couldn't make it work with my json file either. Here you provided a sample of fixed data, however, I need to use recursion to find all children objects.
@bdemirka Go ahead and use recursion with what I provided and update your question with where you're stuck.
0

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

6 Comments

However, it has nothing with the slow rendering problem. I'm thinking about it right now. Just posted it to share it with you and check if it solve you issue or not
Well, the code in sandbox doesn't work, gives an error and it doesn't render the options. I am not sure what causes the problem though, please let me know.
Ops sorry. pls check the link again
now it gives the same result as mine, I mean it should render the options like the first picture, so users can select. Now It renders all at once.
Yes I said that. It just removed <li> cannot appear as a descendant of <li> error
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.