Search code examples
javascriptreactjstypescriptreact-router-dom

Problem using shared Layout for menu in React-Router-DOM v6


I am trying to build an APP in which the side menu items will be different on different page Layouts. I do not want to hardcode the link instead trying to make it reusable. But the menu is not expanding when wrapping it with the react-router-dom.

TestMenuAccordion.tsx:

import { useState } from "react";

const Accordion = ({
  controllerElement,
  contentDescription,
  children,
}: any) => {
  const [isExpanded, setIsExpanded] = useState(false);

  return (
    <div className="w-full mt-4">
      <div
        aria-expanded={isExpanded}
        aria-controls={contentDescription}
        aria-label={(isExpanded ? "hide " : "show ") + contentDescription}
        onClick={() => setIsExpanded((prevIsExpanded) => !prevIsExpanded)}
      >
        {controllerElement(isExpanded)}
      </div>
      {isExpanded && (
        <div id={contentDescription} className="w-full">
          {children}
        </div>
      )}
    </div>
  );
};

export default Accordion;

TestMenu.tsx

import Accordion from "./TestMenuAccordion";
// import "./SideNav.css";
import { Link } from "react-router-dom";

export function SideNavEx(
  props: any,
  { children }: { children: React.ReactNode },
) {
  return (
    <Accordion
      controllerElement={(isExpanded: any) => (
        <button className={isExpanded ? "selected" : "not-selected"}>
          {props.label} {isExpanded ? "-" : "+"}
        </button>
      )}
      contentDescription="menu items"
    >
      <ul className="sideNav">{children}</ul>
    </Accordion>
  );
}

export function GeneralMenuItems(props: any) {
  return (
    <Link to={props.url}>
      <li>{props.text}</li>
    </Link>
  );
}

PageLayout.tsx

import { Outlet } from "react-router-dom";
import { SideNavEx, GeneralMenuItems } from "./TestMenu";

const PageLayout = () => {
  return (
    <>
      <section className="container pt-5 pb-40 px-4 mx-auto grid md:px-8 md:grid-cols-10 md:text-left">
        <div className="px-4 col-span-10 md:col-span-5 lg:col-span-3">

          <SideNavEx label={"General side Menu "}>
            <GeneralMenuItems url={"/Generalmenu1"} text={"Genetal Item 1"} />
            <GeneralMenuItems url={"/Generalmenu2"} text={"Genetal Item 2"} />
            <GeneralMenuItems url={"/Generalmenu3"} text={"Genetal Item 3"} />
          </SideNavEx>

        </div>
        <div className="px-4 pt-4 col-span-10 md:col-span-5 lg:col-span-7">
          <Outlet />
        </div>
      </section>
    </>
  );
};

export default PageLayout;

I am working with React version 18.2 and React-Router-DOM version 6.21.


Solution

  • It would seem you have mis-declared the SideNavEx component. React components take a single props object, but you have declared that SideNavEx takes two arguments.

    Based on the usage it seems you should just remove the first arg, props: any, leaving a single props object with a destructured children prop typed as React.ReactNode, and add the missing label prop.

    export function SideNavEx(
      { children, label }: { children: React.ReactNode; label: string }
    ) {
      return (
        <Accordion
          controllerElement={(isExpanded: boolean) => (
            <button className={isExpanded ? "selected" : "not-selected"}>
              {label} {isExpanded ? "-" : "+"}
            </button>
          )}
          contentDescription="menu items"
        >
          <ul className="sideNav">{children}</ul>
        </Accordion>
      );
    }