RRR uzumaki
RRR uzumaki

Reputation: 1328

Closing a list item dropdown on selecting an item or clicking outside it

I have made a dropdown in my react js project with list items in it (Dropdown items are shown by ul and li tags here).

The issue i am facing is that on selecting any item the value in state changes but the dropdown doesn't close not do it closes when i click anywhere outside of it. Please help me out, here is the working codesandbox url for the same repo

Check here

Also i am sharing the code below .

App.js

import React, { Component, useState } from "react";
import Dropdown from "./dropdown";
import "./dropdown.css";

const App = () => {
  const [value, setValue] = useState("");
  const [showDropdown, setShowDropdown] = useState(false);

  const selectedValue = (value) => {
    console.log(value);
    setValue(value);
  };
  const onSelectItem = () => {
    console.log("12");
    setShowDropdown(false);
  };

  return (
    <section className="shadow-border" id="FlightsForm">
      <div className="top-sec d-flex">
        <div className="left-side">
          <span
            className="custm-dropdown"
            onClick={() => setShowDropdown(true)}
          >
            <span style={{ backgroundColor: "grey", color: "white" }}>
              {value.tripType}
            </span>
            <span className="dropdown-icon"> </span>
            <Dropdown
              selectedValue={selectedValue}
              onSelectItem={onSelectItem}
              showDropdown={showDropdown}
            />
          </span>
        </div>
      </div>
    </section>
  );
};

export default App;

Dropdown.js

import React from "react";
import { useState, useEffect } from "react";

const TripTypeDropdown = (props) => {
  const [values, setValues] = useState([
    { tripType: "One Way", value: 1 },
    { tripType: "Return", value: 2 },
    { tripType: "Multi- City", value: 3 },
  ]);
  const [selectedItem, setSelectedItem] = useState({
    tripType: "One Way",
    value: 1,
  });
  useEffect(() => {
    props.selectedValue(selectedItem);
    console.log(selectedItem);
  }, [selectedItem]);

  const selectItemFromList = (index) => {
    const itemSelected = values[index];
    setSelectedItem(itemSelected);
    props.onSelectItem();
  };
  const getActiveClassName = (item) => {
    if (selectedItem) {
      if (item.tripType == selectedItem.tripType) return "active";
      else return "";
    }
  } ;
  return (
    <React.Fragment>
      <div
        className={`dropdown-modal sm-modal ripple trip-type-dropdown`}
        style={{ display: `${props.showDropdown ? "block" : "none"}` }}
      >
        <ul>
          {console.log(values)}
          {values.map((item, index) => (
            <li
              className={getActiveClassName(item)}
              onClick={() => selectItemFromList(index)}
              key={index}
               style={{backgroundColor:'yellow',border:"1px solid black",listStyle:"none"}}
            >
              {item.tripType}
            </li>
          ))}
        </ul>
      </div>
    </React.Fragment>
  );
};

export default TripTypeDropdown;

Upvotes: 1

Views: 788

Answers (2)

Rajdeep D
Rajdeep D

Reputation: 3900

You need to stop bubbling up of click event from your dropdown component to it's parent span element. On click you need to pass thevent argument and call stopPropagation function of event object

Here is the condesandbox

Dropdown.js

const selectItemFromList = (e,index) => {
    e.stopPropagation();

...

<li
     className={getActiveClassName(item)}
     onClick={(e) => selectItemFromList(e,index)}
     key={index}

Also added code for outside click.

const ref = useRef();

...

useEffect(() => {
    document.addEventListener("click", handleDropdownClick);
  }, [ref]);

const handleDropdownClick = (e) => {
    e.stopPropagation();
    if (ref.current && ref.current.contains(e.target)) {
      setShowDropdown(true);
    } else {
      setShowDropdown(false);
    }
  };

...

<span
     ref={ref}
     className="custm-dropdown"
     onClick={handleDropdownClick}
 >

Upvotes: 3

Drew Reese
Drew Reese

Reputation: 202608

You should stop the propagation of the click event from the list item to the outer span, this is defeating any attempts to toggle the dropdown closed again from the parent.

const selectItemFromList = (index) => (e) => {
  e.stopPropagation();
  const itemSelected = values[index];
  setSelectedItem(itemSelected);
  props.onSelectItem();
};

...

<li
  key={index}
  ...
  onClick={selectItemFromList(index)}
  ...
>
  {item.tripType}
</li>

To handle outside clicks you attach a React ref to the dropdown div and use an useEffect hook to add an onClick event listener to the widow object and check that the onClick's event target is not contained within the dropdown div.

useEffect(() => {
  const outsideClickHandler = (e) => {
    if (props.showDropdown && !outsideRef.current?.contains(e.target)) {
      props.onSelectItem(false);
    }
  };

  window.addEventListener("click", outsideClickHandler);

  return () => window.removeEventListener("click", outsideClickHandler);
}, [props]);

...

<div
  ref={outsideRef}
  ...
>
  ...
</div>

Demo

Edit closing-a-list-item-dropdown-on-selecting-an-item-or-clicking-outside-it

Upvotes: 1

Related Questions