1

I have researched and tried to implement some of the solutions provided but I failed when trying to achieve this. I was able to make my dropdown menu and click so each submenu will open and close when its parent is clicked. I would like to have an opened submenu to be closed when a different menu is clicked, so I don´t have all of them stacked at the menu bar. Could someone point out how can I achieve this? Thank you for helping me.

Menu.js

import React from 'react'
import MenuItem from '../MenuItem';
import { SidebarData } from '../../helpers/SidebarData';
import * as C from './styles';

const Menu = () => {

    return (
        <C.Container>
            <C.MenuArea>
                {SidebarData.map((item, index) => {
                    return <MenuItem item={item} key={index} />;
                })}
            </C.MenuArea>
        </C.Container>
    )
};

export default Menu

MenuItem.js

import React, { useState } from 'react';
import { NavLink } from 'react-router-dom';
import * as C from './styles';


const MenuItem = ({ item }) => {
    const [opened, setOpened] = useState(false);

    const showSubnav = () => setOpened(!opened);

    return (
        <C.NavUnlisted>
            <NavLink to={item.path} onClick={item.subNav && showSubnav} activeClassName='current' exact={item.path === '/' ? true : false} >
                <C.SidebarLink>
                    <div>
                        {item.icon}
                        <C.SidebarLabel>{item.title}</C.SidebarLabel>
                    </div>
                    <div>
                        {item.subNav && opened
                            ? item.iconOpened
                            : item.subNav
                                ? item.iconClosed
                                : null}
                    </div>
                </C.SidebarLink>
            </NavLink>
            {opened &&
                item.subNav.map((item, index) => {
                    return (
                        <NavLink to={item.path} key={index} activeClassName='current' >
                            <C.DropdownLink>
                                {item.icon}
                                <C.SidebarLabel>{item.title}</C.SidebarLabel>
                            </C.DropdownLink>
                        </NavLink>
                    );
                })}
        </C.NavUnlisted>
    );
};

export default MenuItem;

BraulioPortela
  • 199
  • 1
  • 10

3 Answers3

1

I was able to find a solution. I added an ID for each Menu so I could change the state based upon it. I had all the menu in a single component. It didn't seem to be necessary to have a separate component for the Menu Item. Here is my code:

import React, { useState } from 'react'
import { SidebarData } from '../../helpers/SidebarData';
import * as C from './styles';
import { NavLink } from "react-router-dom";

const Menu = () => {

    const [open, setOpen] = useState('');
    const toggle = (id) => setOpen(id);

    return (
        <C.Container>
            <C.MenuArea>
                {SidebarData.map((item, index) => (
                    <C.NavUnlisted key={index}>
                        <NavLink to={item.path} onClick={() => toggle(item.navId)} activeClassName='current' exact={item.path === '/' ? true : false}>
                            <C.SidebarLink>
                                <div>
                                    {item.icon}
                                    <C.SidebarLabel>{item.title}</C.SidebarLabel>
                                </div>
                            </C.SidebarLink>
                        </NavLink>
                        {open === item.navId && (
                            <div>
                                {item.subNav.map((item, index) => (
                                    <NavLink to={item.path} key={index} activeClassName='current' >
                                        <C.DropdownLink>
                                            {item.icon}
                                            <C.SidebarLabel>{item.title}</C.SidebarLabel>
                                        </C.DropdownLink>
                                    </NavLink>
                                ))}
                            </div>
                        )}
                    </C.NavUnlisted>
                ))}
            </C.MenuArea>
        </C.Container>
    )
};

export default Menu

BraulioPortela
  • 199
  • 1
  • 10
0

Try to close the menu when you click outside your menu component, if it's a solution you're interested in you can learn more about how to achieve this in react there :

https://stackoverflow.com/a/42234988/16956436

Jimmy Soussan
  • 652
  • 3
  • 14
  • Hi! Thanks for the help. Wouldn't this approach close the menu when clicked any where on the page? I was looking to have it closed only when another menu item is clicked. – BraulioPortela Nov 05 '21 at 21:02
  • Hey! yes if you want your menu to stay open when you click somwhere else that is not a menu it won't be enough ... – Jimmy Soussan Nov 05 '21 at 21:04
0

An elegant way to handle this would be to keep track of the currently opened submenu in the Menu component and displaying/hiding the submenus depending on a prop passed down from the parent component.

import React from 'react'
import MenuItem from '../MenuItem';
import { SidebarData } from '../../helpers/SidebarData';
import * as C from './styles';

const Menu = () => {
    const [currentlyOpen, setCurrentlyOpen] = useState(null);

    return (
        <C.Container>
            <C.MenuArea>
                {SidebarData.map((item, index) => {
                    return <MenuItem item={item} key={index} isOpen={index === currentlyOpen} onClick={() => setCurrentlyOpen(index)} />;
                })}
            </C.MenuArea>
        </C.Container>
    )
};

export default Menu

You would then call handleClick with the respective index in MenuItem.js.

import React, { useState } from 'react';
import { NavLink } from 'react-router-dom';
import * as C from './styles';


const MenuItem = ({ item, onClick: handleClick }) => {
    const [opened, setOpened] = useState(false);

    const showSubnav = () => setOpened(!opened);

    return (
        <C.NavUnlisted>
            <NavLink to={item.path} onClick={item.subNav && handleClick} activeClassName='current' exact={item.path === '/' ? true : false} >
                <C.SidebarLink>
                    <div>
                        {item.icon}
                        <C.SidebarLabel>{item.title}</C.SidebarLabel>
                    </div>
                    <div>
                        {item.subNav && opened
                            ? item.iconOpened
                            : item.subNav
                                ? item.iconClosed
                                : null}
                    </div>
                </C.SidebarLink>
            </NavLink>
            {opened &&
                item.subNav.map((item, index) => {
                    return (
                        <NavLink to={item.path} key={index} activeClassName='current' >
                            <C.DropdownLink>
                                {item.icon}
                                <C.SidebarLabel>{item.title}</C.SidebarLabel>
                            </C.DropdownLink>
                        </NavLink>
                    );
                })}
        </C.NavUnlisted>
    );
};

export default MenuItem;

aside
  • 722
  • 4
  • 20
  • Hi. Thank you for the help. I will try this. Although I am not sure how to call the handleClick prop inside MeniItem.js with the respective index. I will give it a try. Thanks ever so much. – BraulioPortela Nov 05 '21 at 21:05
  • You can either pass the index or (even better) just add a click handler in the `Menu` component. `handleClick` would then become `onClick` and you could simply use the `index` value passed into the arrow function of map with no arguments. I've adjusted the answer. – aside Nov 05 '21 at 22:16
  • When I pass the `isOpen` prop to `MenuItem` and I `console.log(isOpen)` inside `MenuItem.js`, I always get as `False`, even if the menu is opened. I guess the state is not changing. Shouldn't the state change when the menu is clicked and opened to `True`? – BraulioPortela Nov 05 '21 at 23:12
  • Perhaps your `MenuItem` component is not triggering `onClick`? I've added the `MenuItem` component to clarify. – aside Nov 05 '21 at 23:21
  • Still no luck. When I implement that the submenus won't open. Not sure what I'm missing. I really appreciate you helping me to find a solution. – BraulioPortela Nov 06 '21 at 01:19