I developed the following code inspired by several attempts on the net to create a nested top menu in React. The code is behaving as expecting except for when selecting the last item in the recursive menu, it only closes one level of the menu and leaves the rest of the menu still showing. Pressing outside the menu several times would eventually close all menus. Is there a way to close all the submenus once the user selects the last menu item in the tree?
How the current menu looks like
// menuItems.js
export const menuItems = [
{
title: "Home",
},
{
title: "Services",
submenu: [
{
title: "web design",
},
{
title: "web development",
submenu: [
{
title: "Frontend",
},
{
title: "Backend",
submenu: [
{
title: "NodeJS",
},
{
title: "PHP",
},
],
},
],
},
{
title: "SEO",
},
],
},
{
title: "About",
submenu: [
{
title: "Who we are",
},
{
title: "Our values",
},
],
},
];
// App.js
import { Box } from "@mui/material";
import * as React from "react";
import { useState } from "react";
import MainMenu from "./MainMenu";
function App() {
const [selectedMenuItem, setselectedMenuItem] = useState("");
return (
<>
<MainMenu setselectedMenuItem={setselectedMenuItem} />
<Box sx={{ bgcolor: "yellow", mt: "300px" }}>
<p>
Item Selected from Menu is{" "}
<span style={{ color: "blue", fontSize: 32, fontWeight: "bold" }}>
{selectedMenuItem}
</span>
</p>
</Box>
</>
);
}
export default App;
// MainMenu.js
import { Box } from "@mui/material";
import * as React from "react";
import MenuDropDown from "./MenuDropDown";
import { menuItems } from "./menuItems"; // Holds the structure of the menu
export default function MainMenu({ setselectedMenuItem }) {
const depthLevel = 0;
return (
<Box sx={{ display: "flex", flexDirection: "row"}}>
{menuItems.map((menu, index) => {
return (
<MenuDropDown
key={index}
menuDropDown={menuItems[index]}
setselectedMenuItem={setselectedMenuItem}
depthLevel = {depthLevel}
/>
);
})}
</Box>
);
}
// Holds one top level menu option as a button and its underneath menu items
// MenuDropDown.js
import React from "react";
import Menu from "@mui/material/Menu";
import MenuItem from "@mui/material/MenuItem";
import ListItemText from "@mui/material/ListItemText";
import ListItemIcon from "@mui/material/ListItemIcon";
import ArrowDropDownIcon from "@mui/icons-material/ArrowDropDown";
import ArrowRightIcon from "@mui/icons-material/ArrowRight";
const MenuDropDown = ({ menuDropDown, setselectedMenuItem, depthLevel }) => {
const [anchorEl, setAnchorEl] = React.useState(null);
const open = Boolean(anchorEl);
const handleClick = (event) => {
setAnchorEl(event.currentTarget);
setselectedMenuItem(event.currentTarget.innerText);
};
const handleClose = (event) => {
setselectedMenuItem(event.currentTarget.innerText);
setAnchorEl(null);
};
let currentDepthLevel = depthLevel;
return (
<div>
{/* use an immediately-invoked function expression (IIFE). */}
{(() => {
// Create an arrow function and call it. That was the only way
if ("submenu" in menuDropDown) {
currentDepthLevel = depthLevel + 1;
return (
<div>
{/* Add an arrow down to indicate submenu presence */}
<MenuItem sx={{ mr: 0, pr: 0 }} onClick={handleClick}>
<ListItemText>{menuDropDown.title}</ListItemText>
<ListItemIcon>
{currentDepthLevel === 1 ? (
<ArrowDropDownIcon />
) : (
<ArrowRightIcon />
)}
</ListItemIcon>
</MenuItem>
{/* if there is a submenu array, then display it */}
<Menu
id="submenu"
anchorEl={anchorEl}
open={open}
onClose={handleClose}
anchorOrigin={
currentDepthLevel === 1
? {
vertical: "bottom",
horizontal: "left",
}
: {
vertical: "bottom",
horizontal: "center",
}
}
>
{menuDropDown.submenu.map((submenu, index) => {
if ("submenu" in submenu) {
return (
<MenuDropDown
key={index}
menuDropDown={submenu}
setselectedMenuItem={setselectedMenuItem}
depthLevel={currentDepthLevel}
/>
);
}
return (
<MenuItem key={index} onClick={handleClose}>
{submenu.title}
</MenuItem>
);
})}
</Menu>
</div>
);
} else {
return (
<MenuItem onClick={handleClick}>{menuDropDown.title}</MenuItem>
);
}
})()}
</div>
);
};
export default MenuDropDown;
// Package.json
{
"name": "mongo-form",
"version": "0.1.0",
"private": true,
"dependencies": {
"@emotion/react": "^11.10.4",
"@emotion/styled": "^11.10.4",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.3.0",
"@testing-library/user-event": "^13.5.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.3.0",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}