Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

useRef.current.contains is not a function

I have a nav menu built with material-ui/core in Navbar.

I use useRef to track the position of clicked button on toggle menu close. anchorRef.current.contains(event.target)

And I am getting 'Uncaught TypeError: anchorRef.current.contains is not a function' .

I tried 'Object.values(anchorRef.current).includes(event.target)' instead, it always returns false.

-- update --

anchorRef.current.props Object.

withStyles {
    props:{
     aria-haspopup: "true"
     aria-owns: undefined
     children: "계정"
     className: "nav-menu--btn"
     onClic: f onClick()
     get ref: f()
      isReactWarning: true
      arguments: (...)
      caller: (...)
      length: 0
      name: "warnAboutAccessingRef"
     ...
    }, context{...}, refs{...}, ...}

ToggleMenuList

const ToggleMenuList = ({ navAdminList, navAdminItems, classes }) => {
  const [activeId, setActiveId] = useState(null);
  const anchorRef = useRef(null);
  const handleToggle = id => {
    setActiveId(id);
  };
  const handleClose = event => {
    if (anchorRef.current && anchorRef.current.contains(event.target)) {
      return;
    }
    setActiveId(null);
  };

  return (
    <React.Fragment>
      <div className={`nav-menu--admin ${classes.root}`}>
        {navAdminList.map(e => (
          <div key={e.id}>
            <Button
              ref={anchorRef}
              aria-owns={activeId === e.id ? 'menu-list-grow' : undefined}
              aria-haspopup="true"
              onClick={() => handleToggle(e.id)}
            >
              {e.name}
            </Button>
            {activeId === e.id && (
              <ToggleMenuItems
                id={e.id}
                activeId={activeId}
                handleClose={handleClose}
                anchorRef={anchorRef}
                items={navAdminItems[e.id]}
              />
            )}
          </div>
        ))}
      </div>
    </React.Fragment>
  );
};

export default withStyles(styles)(ToggleMenuList);

ToggleMenuItems

 const ToggleMenuItems = ({
      listId,
      activeId,
      handleClose,
      anchorRef,
      items,
    }) => {
      const isOpen = activeId === listId;
      const leftSideMenu = activeId === 3 || activeId === 4 ? 'leftSideMenu' : '';
    
      return (
        <Popper
          open={isOpen}
          anchorEl={anchorRef.current}
          keepMounted
          transition
          disablePortal
        >
          {({ TransitionProps, placement }) => (
            <Grow
              {...TransitionProps}
              style={{
                transformOrigin:
                  placement === 'bottom' ? 'center top' : 'center bottom',
              }}
              className={`toggle-menu ${leftSideMenu}`}
            >
              <Paper id="menu-list-grow">
                <ClickAwayListener
                  onClickAway={handleClose}
                >
                  <MenuList className="toggle-menu--list">
                    {items.map(e => (
                      <MenuItem
                        key={e.id}
                        className="toggle-menu--item"
                        onClick={handleClose}
                      >
                        <Link
                          to={e.to}
                          className="anchor td-none c-text1 toggle-menu--link"
                        >
                          {e.name}
                        </Link>
                      </MenuItem>
                    ))}
                  </MenuList>
                </ClickAwayListener>
              </Paper>
            </Grow>
          )}
        </Popper>
      );
    };
    
    export default ToggleMenuItems;
react: ^16.8.6
react-dom: ^16.8.6
react-router-dom: ^4.3.1
@material-ui/core: ^3.1.2
like image 898
Jiah827 Avatar asked Nov 07 '22 16:11

Jiah827


1 Answers

I assume your ToggleMenuItems sets up global(document-level?) event listener on click to collapse Menu on clicking somewhere outside.

And you have a sibling button element. Clicking on that you want to keep menu expanded, right? So that was the point to use .contains in onClick to check if we are clicked outside of ToggleMenuItems but in scope of specific Button. The reason why it does not work: <Button> is custom class-based React component so it returns React component instance in ref. And it does not have any DOM-specific methods like .contains

You can rework you current approach: just stop bubbling event in case Button has been clicked. It would stop global event handler set by ToggleMenuItems to react.

const stopPropagation = (event) => event.stopPropagation();

const ToggleMenuList = ({ navAdminList, navAdminItems, classes }) => {
  const [activeId, setActiveId] = useState(null);
  const anchorRef = useRef(null);
  const handleToggle = id => {
    setActiveId(id);
  };
  const handleClose = event => {
    setActiveId(null);
  };

  return (
    <React.Fragment>
      <div className={`nav-menu--admin ${classes.root}`}>
        {navAdminList.map(e => (
          <div key={e.id}>
            <div onClick={stopPropagation}>
              <Button
                aria-owns={activeId === e.id ? 'menu-list-grow' : undefined}
                aria-haspopup="true"
                onClick={() => handleToggle(e.id)}
              >
                {e.name}
              </Button>
            </div>
            {activeId === e.id && (
              <ToggleMenuItems
                id={e.id}
                activeId={activeId}
                handleClose={handleClose}
                anchorRef={anchorRef}
                items={navAdminItems[e.id]}
              />
            )}
          </div>
        ))}
      </div>
    </React.Fragment>
  );
};

export default withStyles(styles)(ToggleMenuList);

I've put stopPropagation handler outside since it does not depend on any internal variable.

like image 109
skyboyer Avatar answered Nov 15 '22 05:11

skyboyer