Опция щелчка в пользовательском раскрывающемся списке [Компонент React с внешней функцией щелчка и массивом ссылок]

У меня есть компонент с пользовательским раскрывающимся списком, и функция закрытия раскрывающегося списка при нажатии в любом месте за пределами компонента работает так, как должна (см. Ниже):

const [isDropdownOpen, setIsDropdownOpen] = useState(false);
const dropdownRef = useRef(null);

useEffect(() => {
      const handleClickOutside = (e) => {
        if (dropdownRef.current && !dropdownRef.current.contains(e.target)) {
          setIsDropdownOpen(false)
        }
      }
      document.addEventListener("mousedown", handleClickOutside);
      
      return () => { //cleanup function
        document.removeEventListener("mousedown", handleClickOutside);
      };
    });

const toggleDropDown = () => {
   setIsDropdownOpen(!isDropdownOpen)
}

return (
 <div onClick={toggleDropDown}> click here
  {isDropdownOpen && (
    <div ref={dropdownRef}>
        <div onClick={handleOption2}>Option 1</div>
        <div onClick={handleOption2}>Option 2</div>
    </div>
  )}
</div> 
)

Когда я динамически создаю компонент для каждого элемента в массиве, вместо этого я использую массив ссылок и пишу функцию внешнего клика, как показано ниже:

const [allDropdownOpen, setAllDropdownOpen] = useState([]);
const dropdownRefs = useRef([]);

// set false dropdown states for all items in array (the array is fetched through an API call)
useEffect(() => {
 if(fetchedArray) {
  let arr = [];
  fetchedArray.forEach(() => arr.push(false));
  setAllDropdownOpen(arr)
 }
}, [fetchedArray])

useEffect(() => {
    let arr = [];
    if(fetchedArray) {
      fetchedArray.forEach(() => arr.push(false)); //create false states for all items in array
    }

    const handleClickOutside = (e) => {
       if(dropdownRefs.current.some(ref => ref && !ref.contains(e.target))) {
          setAllDropdownOpen(arr) //close all dropdowns
       }
    }
    document.addEventListener("mousedown", handleClickOutside);
      
    return () => { //cleanup function
       document.removeEventListener("mousedown", handleClickOutside);
    };
});

const toggleDropDown = (index) => {
 //change dropdown state for selected item
 const arr = [...allDropdownOpen];
 arr[index] = !arr[index];
 setAllDropdownOpen([...arr]);
}

return (
 <div>
  {fetchedArray.map((data, i) => (
    <div 
      onClick={() => toggleDropDown(i)} 
      ref={el => (dropdownRefs.current[i] = el)}
    > 
      click here
    
     {allDropdownOpen[i] && (
       <div>
        <div onClick={handleOption2}>Option 1</div>
        <div onClick={handleOption2}>Option 2</div>
       </div>
     )}
   </div> 
  )}
</div>
)

Хотя это работает и раскрывающиеся списки закрываются, проблема, с которой я сталкиваюсь сейчас, заключается в том, что когда я выбираю параметр в раскрывающемся списке Вариант 1 или Вариант 2, он не выполняет handleOption1 или handleOption2 . Он просто закрывает все выпадающие списки.

Я подтвердил, что параметры на самом деле активны и выполняются, когда я удаляю хук useEffect с помощью функции handleOutsideClick, поэтому я знаю, что ошибка оттуда.

Как избежать этого и правильно нацелить раскрывающийся список выбранного элемента массива?


person Airah Yusuff    schedule 24.06.2021    source источник


Ответы (1)


ОБНОВЛЕНИЕ : Я НАКОНЕЦ-ТО НАШЕЛ СВОЮ НАСТОЯЩУЮ ОШИБКУ!

Я прикреплял ref не к тому элементу! Он был прикреплен к родителю div со свойством onClick, когда он должен был быть прикреплен к самому выпадающему списку (как это было в коде для одного компонента)????‍♀️:

Итак, эта часть:

return (
 <div>
  {fetchedArray.map((data, i) => (
    <div 
      onClick={() => toggleDropDown(i)} 
      ref={el => (dropdownRefs.current[i] = el)}
    > 
      click here

     {allDropdownOpen[i] && (
       <div>
        <div onClick={handleOption2}>Option 1</div>
        <div onClick={handleOption2}>Option 2</div>
       </div>
     )}
   </div> 
  )}
</div>
)}

должно было:

return (
 <div>
  {fetchedArray.map((data, i) => (
    <div 
      onClick={() => toggleDropDown(i)} 
    > 
      click here

     {allDropdownOpen[i] && (
       <div ref={el => (dropdownRefs.current[i] = el)}>
        <div onClick={handleOption2}>Option 1</div>
        <div onClick={handleOption2}>Option 2</div>
       </div>
     )}
   </div> 
  )}
</div>
)}

Такая крошечная ошибка стоила мне всех мозговых клеток????‍♀️????‍♀️

. .

ПРЕДЫДУЩИЙ ОТВЕТ

После бесконечной ночи поиска своей ошибки я решил пойти по этому пути:

  1. инициализировать состояние currentDropdownIndex,
  2. установить его значение в качестве индекса элемента при каждом щелчке его раскрывающегося списка,
  3. используйте индекс для индивидуального таргетинга на ссылку для элемента
    const Wrapper = () => {
    
    const [allDropdownOpen, setAllDropdownOpen] = useState([]);    
    const [currentDropdownIndex, setCurrentDropdownIndex] = useState(null);
    const dropdownRefs = useRef([]);
    
    // set false dropdown states for all items in array (the array is fetched through an API call)
    useEffect(() => {
     if(fetchedArray) {
      let arr = [];
      fetchedArray.forEach(() => arr.push(false));
      setAllDropdownOpen(arr)
     }
    }, [fetchedArray])
    
    //target ref of clicked dropdown using currentDropdownIndex
    useEffect(() => {
        let arr = [];
        if(fetchedArray) {
          fetchedArray.forEach(() => arr.push(false));
        }
       const handleClickOutside = (e) => {
       if(dropdownRefs.current[currentDropdownIndex] && 
         !dropdownRefs.current[currentDropdownIndex].contains(e.target))) {
          setAllDropdownOpen(arr)
       }
      }
      document.addEventListener("mousedown", handleClickOutside);

      return () => {
        document.removeEventListener("mousedown", handleClickOutside);
      };
});

 //change dropdown state for selected item
const toggleDropDown = (index) => {
 const arr = [...allDropdownOpen];
 arr[index] = !arr[index];
 setAllDropdownOpen([...arr]);
 setCurrentDropdownIndex(index); //set dropdown index to clicked array item
}

return (
 <div>
  {fetchedArray.map((data, i) => (
    <div 
      onClick={() => toggleDropDown(i)} 
      ref={el => (dropdownRefs.current[i] = el)}
    > 
      click here

 {allDropdownOpen[i] && (
   <div ref={dropdownRef}>
    <div onClick={handleOption2}>Option 1</div>
    <div onClick={handleOption2}>Option 2</div>
   </div>
 )}

   </div> 
  )}
</div>
)}

И да, это сработало.

person Airah Yusuff    schedule 24.06.2021