单击自定义下拉列表中的选项 [具有外部点击功能和引用数组的 React 组件]

Clicking option in a custom dropdown [React component with outside click function & array of refs]

我有一个带有自定义下拉菜单的组件,点击组件外任意位置关闭下拉菜单的功能正常工作(见下文):

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”中的选项时,它不会执行 handleOption1handleOption2 。它只是关闭所有下拉菜单。

我已经确认这些选项实际上是可点击的,并且当我使用 handleOutsideClick 函数删除 useEffect 挂钩时它们会执行,所以我知道错误来自那里。

如何避免这种情况并正确定位所单击的数组项的下拉列表?

更新 : 我终于找到了真正的错误!

我将 ref 附加到了错误的元素上!当它应该附加到下拉列表本身时(因为它在单个组件的代码中),它被附加到带有 onClick 属性 的父级 div‍♀️:

所以这部分:

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. 使用索引为项目单独定位 ref
    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>
)}

是的,它奏效了。