Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Add state as property on each item of an array of object

I’m developing an React app that have different user types. Each user has specific items on it’s menu. An item may be a link or a submenu. I want to render buttons for link items and collapses (of react-bootstrap) for submenus. But, to have the Collapse to work properly, I need to add a boolean state on each submenu item.

All menus are separated on a file, this way:

// /utils/Menus.js

export const AdminMenu = [
    { label: "Dashboard", href: "/dashboard" },
    { label: "Users", href: "#", subitems: [
        { label: "All Users", href: "/users" },
        { label: "New User", href: "/users/new" }
    ] },
    { label: "Products", href: "#", subitems: [
        { label: "All Products", href: "/products" },
        { label: "New Product", href: "/products/new" }
    ] },
    { label: "Clients", href: "#", subitems: [
        { label: "All Clients", href: "/clients" },
        { label: "New Client", href: "/clients/new" }
    ] },
    { label: "Orders", href: "/orders", subitems: [
        { label: "All Orders", href: "/orders" },
        { label: "Cancelled", href: "/orders/cancelled" },
        { label: "Latest", href: "/orders/latest" }
    ] },
    { label: "My Profile", href: "/my-profile" },
    { label: "Logout", href: "/logout" }
];

export const ClientMenu = [
    { label: "Dashboard", href: "/dashboard" },
    { label: "My Orders", href: "/orders" },
    { label: "My Profile", href: "/my-profile" },
    { label: "Logout", href: "/logout" }
];

export const ManagerMenu = [
    { label: "Dashboard", href: "/dashboard" },
    { label: "Products", href: "#", subitems: [
        { label: "All Products", href: "/products" },
        { label: "New Product", href: "/products/new" }
    ] },
    { label: "Orders", href: "/orders", subitems: [
        { label: "All Orders", href: "/orders" },
        { label: "Cancelled", href: "/orders/cancelled" },
        { label: "Latest", href: "/orders/latest" }
    ] },
    { label: "My Profile", href: "/my-profile" },
    { label: "Logout", href: "/logout" }
];

And I have a sidebar component that renders the menu according on logged user profile. On this component, I want to add an open state property on each menu item that has the subitems property. This way:

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

// /components/Sidenav.js

[...]
const [userMenu, setUserMenu] = useState([]);

useEffect(() => {
    let menu = [];

    switch(user.role){
        case "admin":
            menu = [...AdminMenu];
            break;
        case "client":
            menu = [...ClientMenu];
            break;
        case "manager":
            menu = [...ManagerMenu];
            break;
    }

    menu = menu.map((item) => {
        if(item.subitems){
            item = {...item, open: useState(false)}
        }
        return item;
    });

    setUserMenu(menu);
}, []);

return (
    <ul>
        {userMenu.map((item, i) => (
            <li key={i}>
                { item.subitems ? (
                <>
                    <button onClick={() => { item.open[1](!item.open[0]) }}>{item.label}</button>
                    <Collapse in={item.open[0]}>\
                        <ul>
                            { item.subitems.map((subitem, j) => (
                                <li key={j}>
                                    <Link href={subitem.href}>{subitem.label}</Link>
                                </li>
                            )) }
                        </ul>
                    </Collapse>
                </>
                ) : (
                    <Link href={item.href}>{item.label}</Link>
                ) }
            </li>
        ))}
    </ul>
);

So, the approach is to try to dynamically add the open state on each item that has subitems. And, on render, use item.open[0] (the state) and item.open[1] (the set state function) to toggle the Collapse’s in.

The problem is: React is throwing an error saying I can’t use useState outside a function component. But when I add the open property on items, I’m inside an useEffect that is on a function component. So where’s the problem?

I’ve tested declaring the menus on the component as constants, and directly attribuing the open state where necessary, and it’s worked as expected.

// /components/Sidenav.js

[...]
const [userMenu, setUserMenu] = useState([]);

const AdminMenu = [
    { label: "Dashboard", href: "/dashboard" },
    { label: "Users", href: "#", open: useState(false), subitems: [
        { label: "All Users", href: "/users" },
        { label: "New User", href: "/users/new" }
    ] },
    { label: "Products", href: "#", open: useState(false), subitems: [
        { label: "All Products", href: "/products" },
        { label: "New Product", href: "/products/new" }
    ] },
    { label: "Clients", href: "#", open: useState(false), subitems: [
        { label: "All Clients", href: "/clients" },
        { label: "New Client", href: "/clients/new" }
    ] },
    { label: "Orders", href: "/orders", open: useState(false), subitems: [
        { label: "All Orders", href: "/orders" },
        { label: "Cancelled", href: "/orders/cancelled" },
        { label: "Latest", href: "/orders/latest" }
    ] },
    { label: "My Profile", href: "/my-profile" },
    { label: "Logout", href: "/logout" }
];

const ClientMenu = [
    { label: "Dashboard", href: "/dashboard" },
    { label: "My Orders", href: "/orders" },
    { label: "My Profile", href: "/my-profile" },
    { label: "Logout", href: "/logout" }
];

const ManagerMenu = [
    { label: "Dashboard", href: "/dashboard" },
    { label: "Products", href: "#", open: useState(false), subitems: [
        { label: "All Products", href: "/products" },
        { label: "New Product", href: "/products/new" }
    ] },
    { label: "Orders", href: "/orders", open: useState(false), subitems: [
        { label: "All Orders", href: "/orders" },
        { label: "Cancelled", href: "/orders/cancelled" },
        { label: "Latest", href: "/orders/latest" }
    ] },
    { label: "My Profile", href: "/my-profile" },
    { label: "Logout", href: "/logout" }
];

useEffect(() => {
    switch(user.role){
        case "admin":
            setUserMenu(AdminMenu);
            break;
        case "client":
            setUserMenu(ClientMenu);
            break;
        case "manager":
            setUserMenu(ManagerMenu);
            break;
    }
}, []);

return (
    [...]
);

But this makes me to repeat the menu constants everywhere I need to show up a user menu. It isn’t the best way to do that.

So, how can I add the open state on menu items, to dynamically render links or collapses?

>Solution :

I think it’s simpler to make it a component with the state such that(i’m freestyling below so the code might not work)

const CollapseMenu = ({
  item,
}) => {
  const [isOpen, setIsOpen] = useState(false)

  return <>
          <button onClick={() => { setIsOpen(!isOpen) }}>{item.label}</button>
          <Collapse in={isOpen}>\
              <ul>
                  { item.subitems.map((subitem, j) => (
                      <li key={j}>
                          <Link href={subitem.href}>{subitem.label}</Link>
                      </li>
                  )) }
              </ul>
          </Collapse>
  </>
}
Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading