简体   繁体   中英

Hide dropdown menu upon clicking outside of it

I'm having an issue with the dropdown menu that I've implemented in a React App.

The dropdown is based on this article: https://blog.campvanilla.com/reactjs-dropdown-menus-b6e06ae3a8fe

Opening & closing the dropdown works fine the dropdown, however when I click on a button in the menu, I get the following error:

TypeError: Cannot read property 'contains' of null

I went back to the blog and have seen that other people have reported the same error in the comments:

Nice one. But just the last part doesn't work. dropDownMenu stay 'null'

One possible solution was to add current to the following line of code:

original version

if (!this.dropdownMenu.contains(event.target)) {

modified:

if (!this.dropdownMenu.current.contains(event.target)) {

I found a similar answer here on StackOverflow where another user was having a problem with a dropdown menu: Cannot read property 'addEventListener' of null on menu but I still can't figure out the error.

InnerHeader.jsx

import React, { Component } from 'react';
import {Link, withRouter} from 'react-router-dom'
import logo from '../images/logo.svg'
import Settings from '../lotties/Settings'

class InnerHeader extends Component {
  constructor() {
  super();
    
  this.state = {
    showMenu: false,
    fade: false
  };
  
  this.showMenu = this.showMenu.bind(this);
  this.closeMenu = this.closeMenu.bind(this);
}


showMenu(event) {
  event.preventDefault();
  
  this.setState({ showMenu: true }, () => {
    document.addEventListener('click', this.closeMenu);
  });
}

closeMenu(event) {
  if (!this.dropdownMenu.current.contains(event.target)) {
    
    this.setState({ showMenu: false }, () => {
      document.removeEventListener('click', this.closeMenu);
    });  
    
  }
}
 
handleLogout = () => {
    localStorage.removeItem('authToken');
    this.setState({
      currentUser: null
    })
    this.props.history.push("/")
  }

render() {
    return (
        <>
        <div id="inner-header">
          <div className="logo">
            <Link to="/dashboard"><img src={logo} height="50px" alt="site logo" /></Link>
          </div>
          <div>
        <button 
          className="menu-button"
          onClick={this.showMenu}>
          <Settings/>
        </button>
        <br />
        {
          this.state.showMenu
          ? (
            <div
            className="drop-down fade-in"
            ref={(element) => {
              this.dropdownMenu = element;
            }}
            >
                <div className="center-it-menu">
                <Link to="/account">  Account </Link>
                <br />
                <Link to="/integrations"> Integrations </Link>
                <br />
                <Link to="/tutorials"> Tutorials </Link>
                <br />
                <Link to="/support"> Support </Link>
                <br />
                <button id="menu-alert" onClick={this.handleLogout}> Logout </button>
                </div>
              </div>
            )
            : (
              null
              )
            }
      </div>
        </div>
      </>
    )
  }
}

export default withRouter(InnerHeader)

Since I'm still learning to program, I don't want to rely on a library for this task or just c&p a solution. Other than a solution, I'd like to know what's causing this and how to fix it since it. Since it's one of the first articles displayed when you google react create a dropdown menu , if anyone else tries to implement this I'd like them to have a good answer to it to.

I must say, ref attribute in described scenario is not needed at all. To have your menus collapsed upon clicking outside of them, you can make use of Element.closest() method which will traverse down the DOM tree to find element with className="menu" among event.target ancestors:

 const { Component } = React, { render } = ReactDOM class Card extends Component { constructor() { super(); this.state = { showMenu: false, }; this.showMenu = this.showMenu.bind(this); this.closeMenu = this.closeMenu.bind(this); } showMenu(event) { event.preventDefault(); this.setState({ showMenu: true }, () => { document.addEventListener('click', this.closeMenu); }); } closeMenu(event) { if (!event.target.closest('.menu')) { this.setState({ showMenu: false }, () => { document.removeEventListener('click', this.closeMenu); }); } } render() { return ( <div> <button onClick={this.showMenu}> Show menu </button> { this.state.showMenu ? ( <div className="menu" > <button> Menu item 1 </button> <button> Menu item 2 </button> <button> Menu item 3 </button> </div> ) : ( null ) } </div> ); } } render (<Card />, document.getElementById('root'))
 <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script><div id="root"></div>

Its because when you don't click on Button then this code is not executing and not initiate this.dropdownMenu hence it is Undefined . In such cases, you should use onChange event and add a listener to it and do whatever you want in that function. May it will help you.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM