skip to Main Content

I’m new to React and I’m building my first headless WordPress site. I’m having trouble fetching submenus from my WordPress database for my React Bootstrap Navigation. The main menu items load fine. I’m using lodash groupby to try to organize the submenus under each parent. The dropdown parent label loads fine as well, but the sub items are empty. I’ve tried a lot of different things that haven’t worked. The main problem I’m having is mapping through the subItems. I get an error TypeError: subItems.map is not a function

If I change the subItems map to {Object.entries(subItems).map((subItem) => ( I don’t get the mapping error, but nothing is returned in the subItems and the console throws a warning:
Warning: Each child in a list should have a unique "key" prop.

I’ve gone down the rabbit hole on each error with no luck. Any guidance would be much appreciated. The code to my nav component is below:

import { Nav, NavDropdown } from "react-bootstrap";
import { client, MenuLocationEnum } from "client";
import _ from "lodash";

function MainNav({}): JSX.Element {
  const { menuItems } = client.useQuery();
  const links = menuItems({
    where: { location: MenuLocationEnum.PRIMARY },
  }).nodes;

  const subItems = _.groupBy(
    links.filter((link) => link.parentId),
    "parentId"
  );  

  return (
    <Nav>
      {links?.map((link) => {
        let navItem;
        if (subItems[link.id]) {
          navItem = (
            <NavDropdown title={link.label} key={link.id}>
              {subItems?.map((subItem) => (
                <NavDropdown.Item
                  href={subItem.url ?? ""}
                  key={subItem.id}
                >    
                  {subItem.label}
                </NavDropdown.Item>
              ))}
            </NavDropdown>
          );
        } else if (!link.parentId) {
          navItem = (
            <Nav.Link href={link.url ?? ""} key={link.id}>
              {link.label}
            </Nav.Link>
          );
        }
        return navItem;
      })}
    </Nav>
  );
}
export default MainNav;

Here’s what is being returned by my "subItems" group variable for one of the main nav items:

[
          "cG9zdDo0Mw==",
          [
               {
                    "__typename": "MenuItem",
                    "id": "cG9zdDoxNDI=",
                    "parentId": "cG9zdDo0Mw==",
                    "url": "https://headless.site.app/sub-link-one/",
                    "label": "Sub Link One"
               },
               {
                    "__typename": "MenuItem",
                    "id": "cG9zdDoxNDM=",
                    "parentId": "cG9zdDo0Mw==",
                    "url": "https://headless.site.app/sub-link-two/",
                    "label": "Sub Link Two"
               }
          ]
     ]
]

2

Answers


  1. Chosen as BEST ANSWER

    This might be a little messy, but it works. The top nav and sub nav items are now loading correctly. I had to dig my sub nav items out of the groupby obect by making it into an array, and then filter them by parentId to get to the proper place under the correct parent. Here's my solution:

    import React from "react";
    import { Nav, NavDropdown } from "react-bootstrap";
    import { client, MenuLocationEnum } from "client";
    import _ from "lodash";
    
    function MainNav({}): JSX.Element {
      const { menuItems } = client.useQuery();
      const links = menuItems({
        where: { location: MenuLocationEnum.PRIMARY },
      }).nodes;
    
      const subItems = _.groupBy(
        links.filter((link) => link.parentId),
        "parentId"
      );
    
      return (
        <Nav>
          {links?.map((link) => {
            let navItem;
            if (subItems[link.id]) {
              navItem = (
                <NavDropdown title={link.label} key={link.id}>
                  {Object.values(subItems).map(function (itemArray) {
                    if (itemArray instanceof Array) {
                      return itemArray.map(function (subItem) {
                        if (link.id === subItem.parentId) {
                          return (
                            <NavDropdown.Item
                              href={subItem.url ?? ""}
                              key={subItem.url}
                            >
                              {subItem.label}
                            </NavDropdown.Item>
                          );
                        }
                      });
                    }
                  })}
                </NavDropdown>
              );
            } else if (!link.parentId) {
              navItem = (
                <Nav.Link href={link.url ?? ""} key={link.id}>
                  {link.label}
                </Nav.Link>
              );
            }
            return navItem;
          })}
        </Nav>
      );
    }
    export default MainNav;
    

  2. In case anyone isn’t using Bootstrap, I thought this could be useful. This is what I used for my NextJS project:

        <nav className={styles.menu} role="navigation">
          <ul>
            {links?.map((link) => {
              let navItem: any;
              if(subItems[link.id]) {
                navItem = (
                  <li title={link.label} key={`${link.label}$-menu`}>
                    <Link href={link.url ?? ''}>
                      <a href={link.url}  aria-haspopup="true">{link.label}<IconChevronDownHeavy className={styles.chevron} fill="currentColor" /></a>
                    </Link>
                    <ul className={styles['subnav-items']}  aria-label="submenu">
                      {Object.values(subItems).map(function(itemArray) {
                        if(itemArray instanceof Array) {
                          return itemArray.map(function (subItem) {
                            if(link.id === subItem.parentId) {
                              return (
                                <li key={`${subItem.label}$-menu`}>
                                  <Link href={subItem.url ?? ''}>
                                    <a href={subItem.url}>{subItem.label}</a>
                                  </Link>
                                </li>
                              );
                            }
                          });
                        }
                      })}
                    </ul>
                  </li>
                );
              } else if(!link.parentId) {
                navItem = (
                  <li href={link.url ?? ""} key={link.id}>
                    <Link href={link.url ?? ''}>
                      <a href={link.url}>{link.label}</a>
                    </Link>
                  </li>
                );
              }
              return navItem;
            })}
          </ul>
        </nav>
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search