Navigating Parent Menus While Submenus are Expanded in React Using Material-UI

My React application includes a dynamic menu component created with Material-UI (@mui) that supports nested menus and submenus. I'm aiming to achieve a specific behavior where users can access other menus (such as parent menus) while keeping a submenu open.

The setup involves clicking on a menu item to either display a submenu or toggle a checkbox state. However, the challenge is to allow navigation back to the parent menu or access top-level menus without closing the existing submenu.

Below is an abbreviated version of the code:

import Menu from '@mui/material/Menu';
import MenuItem from '@mui/material/MenuItem';
import IconButton from '@mui/material/IconButton';
import MenuIcon from '@mui/icons-material/Menu';
import Checkbox from '@mui/material/Checkbox';

//... code continues ...

This implementation enables:

  • Opening a submenu when clicking on a menu item with options.
  • Toggling a checkbox state for menu items without options.

I am seeking guidance on how to achieve the following:

  • Allowing users to navigate between menus with a submenu open.
  • Maintaining the visibility of parent menus while interacting with submenus.

I have already attempted managing states using useState but require further assistance in achieving this desired behavior. Any suggestions or examples on improving menu navigation and state management within this context would be greatly appreciated. Thank you!

https://i.stack.imgur.com/RDpxi.png

Answer №1

To create the functionality where users can navigate to other menus while keeping a submenu open and retaining the state of parent or previous menus when interacting with submenus, you need to update the way state is managed and events are handled in your RecursiveMenu and RecursiveMenuItem components. Follow these steps to adjust your code:

Managing Open Menus State:

Use an array to monitor which menu levels are open. When a submenu is opened, add its ID to the array; when it's closed, remove the ID.

Handling Submenu Events:

Treat clicks on menu items differently depending on whether they have submenus. For items without submenus, toggle the checkbox state as usual. For items with submenus, update the openMenus array to reflect the current menu hierarchy.

Here is how you can update your code to implement these changes:

import React, { useState } from 'react';
import Menu from '@mui/material/Menu';
import MenuItem from '@mui/material/MenuItem';
import IconButton from '@mui/material/IconButton';
import MenuIcon from '@mui/icons-material/Menu';
import Checkbox from '@mui/material/Checkbox';

const RecursiveMenu = ({ menuItems }) => {
  const [openMenus, setOpenMenus] = useState([]);

  const handleClick = (event, menuId) => {
    setOpenMenus((prevOpenMenus) => [...prevOpenMenus, menuId]);
  };

  const handleClose = () => {
    setOpenMenus((prevOpenMenus) => prevOpenMenus.slice(0, -1));
  };

  return (
    <div>
      <IconButton
        aria-controls="simple-menu"
        aria-haspopup="true"
        onClick={(event) => handleClick(event, 'root')}
      >
        <MenuIcon />
      </IconButton>
      <Menu
        id="simple-menu"
        anchorOrigin={{
          vertical: 'top',
          horizontal: 'left',
        }}
        anchorEl={document.getElementById('simple-menu')}
        keepMounted
        open={openMenus.length > 0}
        onClose={handleClose}
      >
        {Object.values(menuItems)
          .filter((menuItem) => menuItem.showing !== false)
          .map((menuItem) => (
            <RecursiveMenuItem key={menuItem.id} menuItem={menuItem} handleClick={handleClick} />
          ))}
      </Menu>
    </div>
  );
};

const RecursiveMenuItem = ({ menuItem, handleClick }) => {
  const hasSubMenu = menuItem.options;
  const [showingState, setShowingState] = useState(menuItem.showing);

  const handleItemClick = (event, menuId) => {
    if (hasSubMenu) {
      handleClick(event, menuId);
    } else {
      setShowingState((prevState) => (prevState + 1) % 3);
    }
  };

  return (
    <>
      <MenuItem
        style={{ display: 'flex', justifyContent: 'space-between' }}
        onClick={(event) => handleItemClick(event, menuItem.id)}
      >
        <span>{menuItem.name}</span>
        {!hasSubMenu && (
          <Checkbox
            checked={showingState === 0}
            indeterminate={showingState === 1}
            onChange={(event) => handleItemClick(event, menuItem.id)}
            inputProps={{ 'aria-label': 'indeterminate-checkbox' }}
          />
        )}
      </MenuItem>
      {hasSubMenu && (
        <Menu
          anchorOrigin={{
            vertical: 'top',
            horizontal: 'right',
          }}
          anchorEl={document.getElementById(menuItem.id)}
          keepMounted
          open={true}
          onClose={() => {}}
        >
          {Object.values(menuItem.options)
            .filter((subMenuItem) => subMenuItem.showing !== false)
            .map((subMenuItem) => (
              <RecursiveMenuItem key={subMenuItem.id} menuItem={subMenuItem} handleClick={handleClick} />
            ))}
        </Menu>
      )}
    </>
  );
};

const App = () => {
  const menuItems = {
    A: { id: "A", name: "A", enabled: false, showing: 0 },
    B: { id: "B", name: "B", enabled: false, showing: 2 },
    C: {
      id: "c",
      name: "C",
      options: {
        C1: { id: "C1", name: "C1", enabled: false, showing: 1 },
        C2: {
          id: "C2", name: "C2", options:
          {
            C21: { id: "C21", name: "C2.1", enabled: false, showing: 0 },
            C22: { id: "C22", name: "C2.2", enabled: false, showing: 2 },
          }
        },
      },
    },
    Shaun: { id: "D", name: "D", enabled: false, showing: 2 },
  };

  return <RecursiveMenu menuItems={menuItems} />;
};

export default App;

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What issue is there with the href attribute in the pug file?

/users/mihir/users/[object%20Object]/file.txt My pug file and JS code are set up to render a pug page with links for directories and files in the specified path. The problem arises when adding "users/" plus a username before the directory or filename whil ...

Guide to activating a CSS attribute for the AJAX tab panel with the use of JavaScript

Currently, I am utilizing the ASP.NET AJAX Tab Container with two tab panels. Each tab panel contains a div tag, and by default, my ActiveTabIndex is set to "0." Now, I am trying to apply CSS properties to the div tags using JavaScript without causing any ...

When downloading text using Angular, the file may not display new line characters correctly when opened in Notepad

When downloading text data as a .txt file in my Angular application using JavaScript code, I encountered an issue. Below is the code snippet: function download_text_as_file(data: string) { var element = document.createElement('a') eleme ...

How can I generate codegen types using typeDefs?

I have been exploring the capabilities of graphql-codegen to automatically generate TypeScript types from my GraphQL type definitions. However, I encountered an issue where I received the following error message: Failed to load schema from code file " ...

Unable to set the correct title view for mobile devices

We have successfully styled the products for desktop view and larger phones, but we are facing challenges in adjusting them properly for smaller phones. Below you will find a photo and corresponding code snippets: /* Products list - view list */ .product ...

Stylish CSS menu design

I am struggling to set up a top menu bar using float: left. How can I achieve the desired behavior for a top menu bar? The code snippet below demonstrates what I have tried so far: * { margin:0; padding: 0; } #menu { background-color: yell ...

Tips on preventing the first letter from being capitalized in an input field

Currently, I am developing a React web application primarily used on mobile devices. We have an input field and our goal is to ensure that the first letter entered is not automatically capitalized. The input field can still contain capital letters, but ...

"Stuck in a Standstill: Express/React Commit

Currently, I have set up an Express backend server on port 5000 along with a React frontend running on port 3000. My goal is to fetch some data from an Express post route and return it to the frontend, however, I am encountering an issue where my Promise n ...

Managing array elements in React: removing and duplicating items

One of my tasks is to incorporate a copy and delete button within a table. My goal is to pass the index from the map to the delete and copy onclick functions, however, I am encountering an issue where copying a row results in it having the same index as th ...

Load pages using ajax with conditional if statements

I am currently developing a website that relies on ajax to load its inner pages. However, I need certain groups of pages to have background and container width changes as well. Is there a way for me to use conditional if statements to modify the class or ...

error": "Unable to access undefined properties (reading 'SecretString')

Encountering the error message Cannot read properties of undefined (reading 'SecretString') when utilizing @aws-sdk/client-secrets-manager? It's a sign that you should consider updating your code to accommodate the latest version. ...

It seems that NextJS 14 is retaining old API request data in its cache, even after the data has been

Currently, I am using NextJS version 14.x. In my /dashboard/page.tsx file, I have implemented a method to fetch data from an API. However, I have encountered an issue where the response seems to be cached by NextJS even when the data is updated on the serv ...

I would like the capability to choose a roster of gods depending on a character's class and moral orientation

I am in the process of developing a character sheet for Dungeons & Dragons, and I'm looking to pass the values from two drop-down menus into a query that will then populate another select list. Despite trying various methods to retrieve the data, I ke ...

What methods are available for modifying nodes generated dynamically?

I have been on a quest for quite some time now to find a way to manipulate HTML content that has been dynamically added. Initially, I fetch a cross-domain website using getJSON: $.getJSON('http://whateverorigin.org/get?url=' + encodeURIComponent ...

CSS Techniques for Smooth Transitions

When the hamburger icon is pressed, a menu appears over the current page with CSS from Glamor. The menu slides in perfectly from the right side of the screen, but I am having trouble getting it to slide out when any part of the menu is clicked. I have def ...

Fade-in effect applied to images upon exposure

Is there a way to fade in an image based on the percentage scrolled, rather than a set number of pixels? I want my website to be responsive and adjust to all screen resolutions. Or perhaps is there a method to have the image fade in when it enters the fiel ...

Tips for setting up Highcharts tooltip.headerFormat using the function getDate() plus 5

I'm facing a little challenge trying to understand how the JavaScript function getDate interacts with Highcharts datetime on xAxis. My goal is to show two dates in the tooltip header, forming a date range like this: 1960/1/1 - 1965/1/1. The first da ...

Error encountered: Unexpected function "getOne" in the dataProvider in react-admin

I am currently working on implementing an expand option with react-admin. Here is a snippet from my AdminPanel.js: class AdminPanel extends React.Component { render() { return ( <div> <Admin dataProvider={ ...

In what way does the Express.js 4 Router facilitate navigation to the 404 error page?

When using the express generator, the code generated in the app.js page includes: app.use('/', routes); app.use('/users', users); // catch 404 and forward to error handler app.use(function(req, res, next) { var err = new Error(&ap ...

Run a PHP function using <button onclick=""> tag

Is it possible to trigger the execution of a PHP script when clicking an HTML button? I am aware that simply calling a PHP function directly from the button's onclick event like this: <button onclick="myPhpFunction("testString")">Button</butt ...