React nested menu. How to build dynamic nested menu from json

Viewed 910

I am making sidebar menu from .json. Menu items are BMW models. example data bellow

My return:

<List>
      {Menuhandler( menuDataHandler(JsonSeries) )}
</List>

MenuDataHandler makes json to this format

    let seriesData =  {
                "name": 1-series,
                "url" : /1-series,
                "children" : [ 
                    {
                    "name": e82,
                    "url" : /1-series/e82,
                     {
                    "name": e87,
                    "url" : /1-series/e87
                    }
                    }
                  ]}, {
                "name": 2-series,
                "url" : /2-series,
                "children" : [ 
                    {
                    "name": e82,
                    "url" : /2-series/e82
                    },{
                    "name": e87,
                    "url" : /2-series/e87
                    }
                  ]},
                {
                "name": 3-series,
                "url" : /3-series,
                "children" : [ 
                    {
                    "name": e82,
                    "url" : /3-series/e82
                    }, {
                    "name": e87,
                    "url" : /3-series/e87
                    }
                  ]},
                    {
                "name": 4-series,
                "url" : /4-series,
                "children" : [ 
                    {
                    "name": e82,
                    "url" : /4-series/e82
                    },
                   {
                    "name": e87,
                    "url" : /4-series/e87
                    }
                  ]}

Menuhandler is build like this.

const Menuhandler = ( menulist ) => { 

return menulist.map( ( oneMenuItem, i ) => {

  //console.log( oneMenuItem.index)
    /* 
    If oneMenuitem does not have children return listitem to go wanted url. 
    */
      if (!oneMenuItem.children) {
        return (
          <div key={i}>
         <ThemeProvider theme={theme}>
    
            <ListItem button>
              <Link to={ oneMenuItem.url } className="dropdown-link">
                <ListItemText inset primary={ oneMenuItem.name }/>
              </Link>
            </ListItem>
          </ThemeProvider>
          </div>
        )
      }
      /*
      If oneMenuItem have children. Return Listitem and Collapse. Inside 
     Collapse call {Menuhandler(oneMenuItem.children)} so inside Collapse 
     will be children. This is Loop will go until there is no childrens and 
     it will return above ListItem
      */

      return (
        <div key={i}>
        <ThemeProvider theme={theme}>
          <div className="dropdownItem">
            <ListItem
              button
              key={oneMenuItem.index}
              onClick={() => handleClick(oneMenuItem.url)}
            >
              <Link to={oneMenuItem.url} className="dropdown-link">
                <ListItemText inset primary={oneMenuItem.name} />
              </Link>
              
              { open[ oneMenuItem.url ] ? 
                    <ExpandLess /> :
                    <ExpandMore />
                  } 
              
            </ListItem>
            <div className="menubar-handler">
            </div>

            <Collapse
              in={open[oneMenuItem.url]}
              unmountOnExit
              className="childrenItem"
            >
              {Menuhandler(oneMenuItem.children)}
            </Collapse>
          </div>
        </ThemeProvider>
        </div>
      );
    });
  };

HandleClick

const handleClick = ( ComingUrl ) => {
  //console.log(ComingUrl)
  setOpen( prevState => ( {...open, [ ComingUrl ]: !prevState[ ComingUrl ] } ) )
}

Now there comes the fun part. Collapse works now in one state. open[oneMenuItem.url]. HandleClick will make only one object open. In there it will add item : value.

*click* {/1-series:true}.
*next click* {/1-series:true, /2-series: true}. 

Now two collapses are open. If i click the same item again it will make value false and vice versa. Then one collapse closes.

I want to make HandleClick that will automatically closes in samelevel items.

So if open has:

{/1-series:true, /2-series: true, /3-series: true,/4-series: true}

This could not happen. How i want it to be.

 // one scenario
{/1-series:true, /2-series: false, /3-series: false,/4-series: false}
// Second one 
{/1-series:false, /2-series: true, /3-series: false,/4-series: false}

Same would be in children level. If /1-series/e82 is true /1-series/e87 will be false.

1 Answers

If I understand your question correctly, you have a list with items that can be clicked. Clicking an item expands it to show more data. Only one item can be expanded at one time. Assuming that's correct, here's one approach:

If only one item can be open at a time, why not model your component state so that you just specify which item is open? That way, you only need to keep track of a single value for your state.

Here's a codesandbox of the example below.

function MenuItem({ open, name, onClick, children }) {
  return <li onClick={() => onClick(name)}>{open ? children : name}</li>;
}

function Menu() {
  const [openItem, setOpenItem] = React.useState(null);
  return (
    <ul>
      <MenuItem open={openItem === "foo"} onClick={setOpenItem} name="foo">
        open foo
      </MenuItem>
      <MenuItem open={openItem === "bar"} onClick={setOpenItem} name="bar">
        open bar
      </MenuItem>
      <MenuItem open={openItem === "baz"} onClick={setOpenItem} name="baz">
        open baz
      </MenuItem>
    </ul>
  );
}
Related