Toggle dropdown menu in reactjs

13,922

Your issue is that clicking on the link will also call the body click listener. It means that your state will go from:

  1. Click on link
  2. Click listener on body called
  3. This.state.open set to false
  4. Render called with this.state.open false
  5. Click listener on the link called
  6. This.state.open set to true
  7. Render called with this.state.open true

e.stopPropagation() doesn't work in React. One workaround would be to:

handleBodyClick: function(e)
{
    if (e.target.nodeName !== 'A') {
       this.setState({isOpen: false});
    }
},

Another way (and better way) would be to have the click listener not on body, but on a div, and make it as big as possible (to be as the same size a body basically).

Here is an example with binding a click on a div instead of body: https://jsfiddle.net/jL3yyk98/

Share:
13,922
user2442241
Author by

user2442241

Updated on June 04, 2022

Comments

  • user2442241
    user2442241 almost 2 years

    I have the following code for a simple dropdown menu on my navbar: https://jsfiddle.net/jL3yyk98/10/

    index.html

    <div id="menu-button"></div>
    

    NavMenu.js

    var NavMenu = React.createClass({
        getDefaultProps: function()
        {
            return {
                isOpen: false
            };
        },
    
        render: function()
        {
            if (this.props.isOpen)
            {
                return (
                    <div className="dropdown">
                        <ul>
                            <li><a href="#">News</a></li>
                            <li><a href="#">About</a></li>
                            <li><a href="#">Guidelines</a></li>
                            <li><a href="#">Exchange</a></li>
                            <li><a href="#">Forum</a></li>
                        </ul>
                    </div>
                );
            }
            return null;
        }
    });
    

    NavMenuButton.js

    var NavMenuButton = React.createClass({
    
        getInitialState: function()
        {
            return {
                isOpen: false
            };
        },
    
        toggleMenu: function(e)
        {
            e.stopPropagation();
            this.setState({isOpen: !this.state.isOpen});
        },
    
        onClose: function()
        {
            this.setState({isOpen: false});
        },
    
        componentDidMount: function ()
        {
            document.body.addEventListener('click', this.onClose);
        },
    
        componentWillUnmount: function ()
        {
            document.body.removeEventListener('click', this.onClose);
        },
    
        render: function()
        {
            return (
                <div>
                    <a onClick={this.toggleMenu} href="#">Menu</a>
                    <NavMenu isOpen={this.state.isOpen} />
                </div>
            );
        }
    
    });
    
    React.render(<NavMenuButton />, document.getElementById('menu-button'));
    

    I understand with my current code that both the toggleMenu method and onClose method are called when the user clicks the menu button to close the menu (since they are also effectively clicking the body); and that the onClose method is called first, meaning that the state is set to false, but then the toggleMenu method is called and it's set back to true. Why is this, and how can I fix it so that clicking the menu button toggles the menu and clicking the body hides the menu?

    If this approach seems wrong what approach should I be using? I'm fairly new to react so I'm still learning what goes where and why.

    Also, I cannot use a full body div as the solution to this, it needs to be a typical dropdown; so if users want to interact with another part of the page (maybe clicking a link), then they can do that.

  • user2442241
    user2442241 almost 9 years
    Unfortunately this does not solve my problem. I would *really rather not have a full body div, so that users can still say, click on links and what not and not be forced to click once to remove the div and then click again to actually use said link. So with that being said, the code you posted above doesn't work since the menu button is wrapped in an anchor tag. I'm open to new methods of toggling options in React, if this one isn't the way to do it, but I think there is a way and I'm just missing it.
  • Jeremy D
    Jeremy D almost 9 years
    I just gave you an answer about what was wrong with an explanation. Sorry if it doesn't solve your problem I was just hoping it would help as I basically did your debugging :)