点击Antd Select下拉选项时,ref误判为外部点击致筛选框关闭
问题描述
我正在开发点击按钮打开筛选框、点击外部区域关闭筛选框的功能,使用了useRef Hook以及Antd的Select、DatePicker等UI组件。功能整体正常,但点击Select或DatePicker弹出的菜单选项时,会被判定为外部点击,导致筛选框关闭,而这些组件实际处于ref的范围内。需求是点击筛选选项时不关闭筛选框。
原代码
import { Card, Select } from "antd"; import { FilterObject, PaymentStatus } from "../../helpers/types"; import { capitalizeString } from "../../helpers/functions"; import { CommonDatePicker } from "../datePicker/CommonDatePicker"; import { CommonButton } from ".."; import { useEffect, useRef, useState } from "react"; import { filterIcon } from "../../../finance/assets"; import { FILTERS } from "../../helpers/constants"; export const Filters = () => { // useStates const [showFilters, setShowFilters] = useState<boolean>(false); const [filters, setFilters] = useState<FilterObject>({ status: "", startDate: "", endDate: "", }); // useRef const filterRef = useRef<any>(null); // useEffect useEffect(() => { document.addEventListener("mousedown", handleClickOutside); () => { document.removeEventListener("mousedown", handleClickOutside); }; }); // functions const handleClickOutside = (event: any) => { if (filterRef.current && !filterRef.current.contains(event.target)) { setShowFilters(false); } }; const handleFilterChange = (name: string, value: string) => { const tempFilters: FilterObject = { ...filters }; if (name === FILTERS.status) { tempFilters.status = value; } console.log(tempFilters); setFilters(tempFilters); }; return ( <div ref={filterRef}> {showFilters && ( <Card className="absolute top-[4.8rem] right-[10rem] text-[#232425] text-[14px] z-50 w-[21rem] bg-[#FFFFFF]"> <p className="font-bold text-lg">Filter By</p> <hr className="mt-4" /> <div className="mt-4"> <p>Select Status</p> <Select className="w-full mt-2" onChange={(e: string) => { handleFilterChange(FILTERS.status, e); }} defaultValue="Select Status" options={[ { value: PaymentStatus.approved, label: capitalizeString(PaymentStatus.approved), }, { value: PaymentStatus.pending, label: capitalizeString(PaymentStatus.pending), }, { value: PaymentStatus.rejected, label: capitalizeString(PaymentStatus.rejected), }, ]} /> </div> <div className="mt-4"> <p>From Date</p> <CommonDatePicker onChange={(e) => { console.log(e); }} inputClass="w-full" /> </div> <div className="mt-4"> <p>To Date</p> <CommonDatePicker onChange={(e: any) => { console.log(e); }} inputClass="w-full" /> </div> <CommonButton title="Submit" onClick={() => {}} buttonClass="[&]:w-full [&]:h-[2.2rem]" /> </Card> )} <img className="cursor-pointer" src={filterIcon} alt="" onClick={() => setShowFilters(!showFilters)} /> </div> ); };
问题原因
Antd的Select、DatePicker组件的下拉菜单默认挂载在document.body下,不属于你定义的filterRef容器范围,所以点击下拉选项时,filterRef.current.contains(event.target)会返回false,触发关闭逻辑。
解决方案
方案1:修改点击判断逻辑,兼容Antd下拉元素
检查点击目标是否属于Antd下拉组件的DOM元素(Antd下拉菜单带有固定类名,比如ant-select-dropdown、ant-picker-dropdown),如果是则不关闭筛选框:
const handleClickOutside = (event: MouseEvent) => { // 检测点击目标是否在Antd下拉组件内 const isInAntdDropdown = event.target instanceof Element && (event.target.closest('.ant-select-dropdown') || event.target.closest('.ant-picker-dropdown')); if (filterRef.current && !filterRef.current.contains(event.target) && !isInAntdDropdown) { setShowFilters(false); } };
方案2:将下拉菜单挂载到filterRef内部
给Antd组件设置getPopupContainer属性,让下拉菜单渲染在filterRef容器中,这样contains就能检测到:
- 给Select添加属性:
<Select getPopupContainer={() => filterRef.current} // 其他原有属性... />
- 给CommonDatePicker透传
getPopupContainer(如果内部封装了Antd DatePicker):
<CommonDatePicker getPopupContainer={() => filterRef.current} // 其他原有属性... />
注意:如果CommonDatePicker是自定义封装组件,需要在组件内部把getPopupContainer传递给Antd的DatePicker组件。
修复useEffect清理函数错误
原代码中useEffect的清理函数没有正确返回,会导致重复绑定事件,修改为:
useEffect(() => { document.addEventListener("mousedown", handleClickOutside); return () => { document.removeEventListener("mousedown", handleClickOutside); }; }, []); // 空依赖数组,确保只绑定一次事件
内容的提问来源于stack exchange,提问作者Sambhav Jain
相关产品推荐
相关产品推荐

