0

I am new to React and want to design a navigation bar with dropdown menu. When I click outside of the dropdown menu, it can be closed. And It works fine.

But the problem is when I click "settings", the dropdown menu is closed as well, with a warning:

Warning: findDOMNode is deprecated in StrictMode. findDOMNode was passed an instance of Transition which is inside StrictMode. Instead, add a ref directly to the element you want to reference.

I think the problem comes from the navItemRef or the open State since when I click "settings", the navItemRef will not contain chilren here {open && children}.

Here is the complete code:

import { PropsWithChildren, useEffect, useRef, useState } from "react";
import { CSSTransition } from "react-transition-group";

import styles from "./styles.module.css";

const NavBar: React.FC<PropsWithChildren> = ({ children }) => {
  return (
    <nav className={styles.navbar}>
      <ul className={styles["navbar-nav"]}>{children}</ul>
    </nav>
  );
};

interface INavItemProps extends PropsWithChildren {
  icon: JSX.Element | string;
}

const NavItem: React.FC<INavItemProps> = ({ icon, children }) => {
  const [open, setOpen] = useState<boolean>(false);

  const navItemRef = useRef<HTMLLIElement>(null);

  // !!! The wrong place
  useEffect(() => {
    const handleOutsideClick = (evt: MouseEvent) => {
      if (!navItemRef.current?.contains(evt.target as Node)) {
        setOpen(false);
      }
    };
    document.addEventListener("click", handleOutsideClick);

    return () => {
      document.removeEventListener("click", handleOutsideClick);
    };
  }, []);

  return (
    <li className={styles["nav-item"]} ref={navItemRef}>
      <a
        href="#"
        className={styles["icon-button"]}
        onClick={() => setOpen(!open)}
      >
        {icon}
      </a>
      {open && children}
    </li>
  );
};

type DropDownItemType = "main" | "center" | "settings";
interface IDropDownItemProps extends PropsWithChildren {
  toMenu?: DropDownItemType;
  leftIcon?: JSX.Element | string;
  rightIcon?: JSX.Element | string;
}

const DropdownMenu: React.FC = () => {
  const [activeMenu, setActiveMenu] = useState<DropDownItemType>("main");
  const [menuHeight, setMenuHeight] = useState<number>(0);
  const dropdownRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const firstChild = dropdownRef.current?.firstChild as HTMLDivElement;
    setMenuHeight(firstChild?.offsetHeight);
  }, []);

  const calcHeight = (el: HTMLElement) => {
    const height = el.offsetHeight;
    setMenuHeight(height);
  };

  const DropDownItem: React.FC<IDropDownItemProps> = ({
    toMenu,
    leftIcon,
    rightIcon,
    children,
  }) => {
    return (
      <a
        href="#"
        className={styles["menu-item"]}
        onClick={() => toMenu && setActiveMenu(toMenu)} // toMenu might be undefined
      >
        <span className={styles["icon-button"]}>{leftIcon}</span>
        {children}
        <span className={styles["icon-right"]}>{rightIcon}</span>
      </a>
    );
  };

  return (
    <div
      className={styles.dropdown}
      style={{ height: menuHeight }}
      ref={dropdownRef}
    >
      <CSSTransition
        in={activeMenu === "main"}
        timeout={500}
        classNames={{
          enter: styles["menu-primary-enter"],
          enterActive: styles["menu-primary-enter-active"],
          exit: styles["menu-primary-exit"],
          exitActive: styles["menu-primary-exit-active"],
        }}
        unmountOnExit
        onEnter={calcHeight}
      >
        <div className={styles.menu}>
          <DropDownItem>profile</DropDownItem>
          <DropDownItem leftIcon={"icon1"} rightIcon={"icon1"} toMenu="settings">
            settings
          </DropDownItem>
        </div>
      </CSSTransition>
      <CSSTransition
        in={activeMenu === "settings"}
        timeout={500}
        classNames={{
          enter: styles["menu-secondary-enter"],
          enterActive: styles["menu-secondary-enter-active"],
          exitActive: styles["menu-secondary-exit-active"],
        }}
        unmountOnExit
        onEnter={calcHeight}
      >
        <div className={styles.menu}>
          <DropDownItem toMenu="main" leftIcon={""}>
            <h2>Back</h2>
          </DropDownItem>
          <DropDownItem leftIcon={"icon3"}>setting 1</DropDownItem>
          <DropDownItem leftIcon={"icon3"}>setting 2</DropDownItem>
          <DropDownItem leftIcon={"icon3"}>setting 3</DropDownItem>
        </div>
      </CSSTransition>
    </div>
  );
};

export { NavBar, NavItem, DropdownMenu };

To be concise, here is the place I think is wrong:

// !!! The wrong place
useEffect(() => {
  const handleOutsideClick = (evt: MouseEvent) => {
    if (!navItemRef.current?.contains(evt.target as Node)) {
      setOpen(false);
    }
  };
  document.addEventListener("click", handleOutsideClick);

  return () => {
    document.removeEventListener("click", handleOutsideClick);
  };
}, []);
Libra
  • 19
  • 1
  • The error you get is from `CSSTransition`, check out `nodeRef` in the docs. It's not related to your problem. – Arkellys Apr 09 '23 at 11:00
  • Some relevant discussion on [this previous answer](https://stackoverflow.com/a/65918908/21146235) – motto Apr 09 '23 at 11:24

0 Answers0