You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

点击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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 03:40:55