1
votes

I have dropdown which is open on btn click.

  toggleAllCategories() {
    this.setState({ isOpenAllCategories: !this.state.isOpenAllCategories });
  }

Also on outside click dropdown is closed.

  handleClickOutside(event) {
    if (this.wrapperRef && !this.wrapperRef.contains(event.target)) {
      this.toggleAllCategories();
    }
  }

But problem is cus I can't open and close dropdown by clicking on button.

When I click two times on the button dropdown is still open, cus of outside click implementation.

How to make togglebleDroopdown on button and close on outside click in same time?

STACKBLITZ live example: https://stackblitz.com/edit/react-coihzv?file=index.js

This is component and whole implementation of dropdown and entire related logic:

class AllCategories extends Component {
  constructor(props) {
    super(props);

    this.state = {
      allCategories: getAllCategoriesList(),
      isOpenAllCategories: false
    };
  }

  componentDidMount() {
    document.addEventListener('mousedown', this.handleClickOutside.bind(this));
  }

  componentWillUnmount() {
    document.removeEventListener('mousedown', this.handleClickOutside.bind(this));
  }

  setWrapperRef(node) {
    this.wrapperRef = node;
  }

  handleClickOutside(event) {
    if (this.wrapperRef && !this.wrapperRef.contains(event.target)) {
      this.toggleAllCategories();
    }
  }

  toggleAllCategories() {
    this.setState({ isOpenAllCategories: !this.state.isOpenAllCategories });
  }

  render() {
    return (
      <Fragment>
        <AllCategoriesButton toggleAllCategories={this.toggleAllCategories} />

        {this.state.isOpenAllCategories ? (
          <div className="all-categories-wrapper" ref={(node) => this.setWrapperRef(node)}>
            <div className="all-categories">
              <ul className="all-categories-list">
                <li className="all-categories-list-item">All Categories</li>
                {this.state.allCategories.map((category) => (
                  <li
                    className={`all-categories-list-item ${
                      category.selected ? 'all-categories-list-item-active' : ''
                    }`}
                  >
                    {category.name}
                  </li>
                ))}
              </ul>
            </div>
          </div>
        ) : (
          ''
        )}
      </Fragment>
    );
  }
}
2

2 Answers

2
votes

You can check the element in which the click was triggered using event.target.tagName.

So to prevent toggling twice you can do the following:

 handleClickOutside(event) {
    if (this.wrapperRef && !this.wrapperRef.contains(event.target) &&event.target.tagName.toLowerCase() !== 'button') {
      this.toggleAllCategories();
    }
  }

EDIT: Another Approach

  • Create a ref for the button lets name it buttonRef.

Now update the handleClickOutside to this:

handleClickOutside(event) {
    if (this.wrapperRef && !this.wrapperRef.contains(event.target) &&event.target !== buttonRef.current) {
      this.toggleAllCategories();
    }
  }

This will be more appropriate way.

Hope this Helps!

1
votes

In case of button click toggleAllCategories is triggered twice. One from the click of the button and another from handleClickOutside .you need to call it from one place