单击自定义下拉列表中的选项 [具有外部点击功能和引用数组的 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”中的选项时,它不会执行 handleOption1
或 handleOption2
。它只是关闭所有下拉菜单。
我已经确认这些选项实际上是可点击的,并且当我使用 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>
)}
这么小的一个错误,让我耗尽了所有的脑细胞♀️♀️
。
.
上一个答案
经过一个没完没了的夜晚寻找我的错误,我决定走这条路:
- 初始化一个
currentDropdownIndex
状态,
- 每当单击下拉菜单时,将其值设置为项目的索引,
- 使用索引为项目单独定位 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>
)}
是的,它奏效了。
我有一个带有自定义下拉菜单的组件,点击组件外任意位置关闭下拉菜单的功能正常工作(见下文):
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
。它只是关闭所有下拉菜单。
我已经确认这些选项实际上是可点击的,并且当我使用 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>
)}
这么小的一个错误,让我耗尽了所有的脑细胞♀️♀️
。 .
上一个答案
经过一个没完没了的夜晚寻找我的错误,我决定走这条路:
- 初始化一个
currentDropdownIndex
状态, - 每当单击下拉菜单时,将其值设置为项目的索引,
- 使用索引为项目单独定位 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>
)}
是的,它奏效了。