1

I have made some dropdown menus:

export default class DropdownMenu extends Component {

    constructor(props) {
        super(props)

        this.state = {
            menuOpen: false,
            highlight: false,
            count: this.props.count | 0
        }
        this.showDropdown = this.showDropdown.bind(this);
    }

    componentDidMount() {

    }

    showDropdown() {
            this.setState({
                menuOpen: !this.state.menuOpen
            });
    }

    render() {
        return <div className="dropdown__menu" onClick={this.showDropdown}>
            {this.props.text} {this.state.count > 0 ? <b>{this.state.count}</b> : ''}
            <div className="dropdown__content" style={this.state.menuOpen ? {'display': 'block'} : {'display': 'none'}}>
                {this.props.children}
            </div>
        </div>
    }
}

The issue is that you can open all of them and leave them open until you click on them again to close them. How do I make it so that any other menu that is open gets closed if another is opened?


Here is where they are implemented:

render() {
        ...
                <div className="filter_container">
                    <DropdownMenu text="New" count={127} disabled/>
                    <DropdownMenu text="Only show">
                        <li>New</li>
                        <li>Old</li>
                    </DropdownMenu>
                    <DropdownMenu text="Other">
                        <li>one</li>
                        <li>two</li>
                    </DropdownMenu>
                    <DropdownMenu text="Sort by">
                        <li>Name</li>
                        <li>Age</li>
                        <li>Value</li>
                    </DropdownMenu>
                </div>
            </div>
...
imperium2335
  • 23,402
  • 38
  • 111
  • 190

4 Answers4

2

You can play with onBlur since it loses focus when another is being cliked.

class DropDown extends React.Component {
  state = {
    isVisible: false
  }
  
  closeMenu = () => {
    this.setState({ isVisible: false })
  }
  
  toggleMenu = () => {
    this.setState(prevState => ({ isVisible: !prevState.isVisible }))
  }
  
  render() {
    const { isVisible } = this.state;
    return (
      <div
        className="dropdown__menu"
        onBlur={this.closeMenu}
        tabIndex={0}
        role="menu"
        onClick={this.toggleMenu}>
        {isVisible ? 'visible' : 'hidden'}
      </div>
    )
  }
}


const App = () => (
  <div>
     <DropDown />
     <DropDown />
  </div>
)
 

ReactDOM.render(<App />, document.getElementById('root'));
.dropdown__menu {
  outline: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
gazdagergo
  • 6,187
  • 1
  • 31
  • 45
0

since react got one way data flow you need to do this in parent component

import React from 'react'

class DropdownParent extends React.Component {

  state = {
    openedDropdown: null,
  };

  render() {
    return (
      <React.Fragment>
        <Dropdown
          name="dropdown1"
          isOpen={this.state.openedDropdown === 1}
          onClick={() => this.setState({ openedDropdown: 1 })}
          onClose={() => this.setState({ openedDropdown: null })}
        />
        <Dropdown
          name="dropdown2"
          isOpen={this.state.openedDropdown === 2}
          onClick={() => this.setState({ openedDropdown: 2 })}
          onClose={() => this.setState({ openedDropdown: null })}
        />
      </React.Fragment>
    )
  }
}

and then you need Dropdown to be based on props.isOpen, not on your state.menuOpen and use props.onClick / props.onClose instead of this.showDropdown

Alternatively you can base on mouse (like onMouseDown onMouseEnter...) or focus (onFocus onBlur) events but this is hard to get mobile friendly and won't ensure you about "only one dropdown at the same moment". more info: https://reactjs.org/docs/events.html

r g
  • 3,586
  • 1
  • 11
  • 27
0

Since being open is not an information encapsulated in your dropdown's state anymore, I would suggest moving up this information up to your parent component's state.

You should now convert your Dropdowns into stateless functions :

const DropdownMenu = ({ menuOpen, count, text, showDropdown }) => ( //props deconstruction
    <div className="dropdown__menu" onClick={showDropdown}>
        {text} 
        {count && <b>{count}</b>} //If you do not want to show anything if a condition is falsy, use the inline if &&
        <div className="dropdown__content" style={{ 'display': menuOpen ? 'block' : 'none' }}> //You can put the ternary condition directly into the JSON
            {this.props.children}
        </div>
    </div>
)

You will now have to store which dopdown is opened in your parent component :

class Parent extends Component {
    state = {
        openedDropdown = null;
    }

And send a callback function to your dropdown :

dropdownClicked = openedDropdown => ev => {
    this.setState({ openedDropdown })
}

<DropdownMenu text="New" count={127} disabled showDropdown={this.dropdownClicked('New')}  menuOpen={this.state.openedDropdown === 'New'}/>
<DropdownMenu text="Only show" showDropdown={this.dropdownClicked('Only')}  menuOpen={this.state.openedDropdown === 'Only'}>
    <li>New</li>
    <li>Old</li>
</DropdownMenu>
<DropdownMenu text="Other" showDropdown={this.dropdownClicked('Other')}  menuOpen={this.state.openedDropdown === 'Other'}>
    <li>one</li>
    <li>two</li>
</DropdownMenu>
<DropdownMenu text="Sort by" showDropdown={this.dropdownClicked('Sort')}  menuOpen={this.state.openedDropdown === 'Sort'}>
    <li>Name</li>
    <li>Age</li>
    <li>Value</li>
</DropdownMenu>
Treycos
  • 7,373
  • 3
  • 24
  • 47
0

I would use redux for that. https://redux.js.org/

This solution allows you to manage state for whole app. It could be used for storing info about opened dropdown, and change it - if necessary.

Piotr P.
  • 74
  • 1