reactjstypescriptnext.jsantd

How to Clear Value of an Ant Design Linked/cascading Dropdown Select Using Next.js/React.js


I'm working on a React form using Ant Design's Form component. The form includes several dropdowns, including facility, specialization, and doctor. The doctor dropdown should be cleared whenever the facility or specialization dropdown values change, and the doctor dropdown should be repopulated based on the new facility and specialization values.

Here’s a summary of the setup:

Issue: Despite calling form.setFieldsValue({ doctorId: undefined }); and setting setDoctor(null); in the useEffect hook, the doctorId dropdown does not clear its value as expected when facility or specialization changes.

Code Example: you can find the code here on Stack Blitz

What I've Tried:

  1. Setting the doctorId field value to undefined or null.
  2. Adding a key prop to the SearchableDropDown to force re-rendering.
  3. Manually setting the doctor state to null.

Question: How can I programmatically clear the doctorId dropdown value when either the facility or specialization dropdown changes in an Ant Design form? What is the correct approach to ensure that the doctor dropdown is cleared and reset based on the new facility and specialization selections?

Any advice or suggestions would be greatly appreciated. Thanks in advance!

enter image description here


Solution

    1. In your app/page.tsx file, you forgot to add form={form} attribute in your antd Form Tag.

    2. doctorId can be reset using form.resetFields(['doctorId']).

    Modify your useEffect for Reset doctorId.

    React.useEffect(() => {
        setDoctor(undefined);
        form.resetFields(['doctorId']);
    }, [facility, specialization]);
    

    Verify the following code:

    NOTE: The code below is for guiding purposes, so it does not include lodash/debounce lib debounce function & Optional chaining (?.).

    Please add it to your code as per requirement.

    { /* Start: Enum & JSON */}
    
    let DDL_Type = {
      Facility: "ddl-facility",
      Doctor: "ddl-doctor",
      Specialization: "ddl-specialization",
    };
    
    let Specialization = [
      {
        label: "Addiction Psychiatry",
        value: "sp_1",
      },
      {
        label: "Aerospace Medicine",
        value: "sp_2",
      },
      {
        label: "Allergy and Immunology",
        value: "sp_3",
      },
      {
        label: "Anatomic Pathology",
        value: "sp_4",
      },
      {
        label: "Blood Banking/Transfusion Medicine",
        value: "sp_5",
      },
      {
        label: "Cardiology",
        value: "sp_6",
      },
      {
        label: "Microbiology",
        value: "sp_7",
      },
      {
        label: "Clinical Neurophysiology",
        value: "sp_8",
      },
      {
        label: "Clinical Pharmacology",
        value: "sp_9",
      },
      {
        label: "Colon and Rectal Surgery",
        value: "sp_10",
      },
    ];
    
    let Facility = [
      {
        label: "Dar-ul-Sehat",
        value: "f_1",
      },
      {
        label: "Dow",
        value: "f_2",
      },
      {
        label: "Indus",
        value: "f_3",
      },
      {
        label: "MOH",
        value: "f_4",
      },
      {
        label: "My Facility",
        value: "f_5",
      },
    ];
    
    let Doctor = [
      {
        _id: "d_1",
        facility: {
          _id: "f_2",
          name: "Dow",
        },
        firstName: "Azhar",
        lastName: "Sadiq",
        specialization: {
          _id: "sp_4",
          name: "Anatomic Pathology",
        },
      },
      {
        _id: "d_2",
        facility: {
          _id: "f_5",
          name: "My Facility",
        },
        firstName: "Haider",
        lastName: "Abbasi",
        specialization: {
          _id: "sp_7",
          name: "Microbiology",
        },
      },
      {
        _id: "d_3",
        facility: {
          _id: "f_5",
          name: "My Facility",
        },
        firstName: "Hammad",
        lastName: "Javeid",
        specialization: {
          _id: "sp_6",
          name: "Cardiology",
        },
      },
      {
        _id: "d_4",
        facility: {
          _id: "f_5",
          name: "My Facility",
        },
        firstName: "Zulqarnain",
        lastName: "jalil",
        specialization: {
          _id: "sp_6",
          name: "Cardiology",
        },
      },
    ];
    
    { /* End: Enum & JSON */}
    
    { /* Start: fetch_ddl Function */}
    
    function fetch_ddl(search, ddl_type, otherQueryParams) {
      if (ddl_type === DDL_Type.Specialization)
        return Specialization.filter(
          (specialization) =>
            !search ||
            specialization.label.toLowerCase().includes(search.toLowerCase())
        );
        
      else if (ddl_type == DDL_Type.Facility)
        return Facility.filter(
          (facility) =>
            !search || facility.label.toLowerCase().includes(search.toLowerCase())
        );
        
      else if (ddl_type == DDL_Type.Doctor)
        return Doctor.filter((doctor) => {
          const matchesSearch =
            !search ||
            doctor.firstName.toLowerCase().includes(search.toLowerCase()) ||
            doctor.lastName.toLowerCase().includes(search.toLowerCase());
    
          const matchesFacility =
            !otherQueryParams.facilityId ||
            doctor.facility._id === otherQueryParams.facilityId;
    
          const matchesSpecialization =
            !specializationId ||
            doctor.specialization._id === otherQueryParams.specializationId;
    
          return matchesSearch && matchesFacility && matchesSpecialization;
        }).map((doctor) => ({
          label: `${doctor.firstName} ${doctor.lastName} (${doctor.specialization.name})`,
          value: doctor._id,
        }));
    }
    
    { /* End: fetch_ddl Function */}
    
    { /* Start: SearchableDropDown Component */}
    
    function SearchableDropDown({
      debounceTimeout = 800,
      onChange,
      ddl_type,
      queryParams,
      value,
      ...props
    }) {
    
      const [fetching, setFetching] = React.useState(false);
      const [options, setOptions] = React.useState([]);
      const fetchRef = React.useRef(0);
      const [internalValue, setInternalValue] = React.useState(value);
    
    
      const debounceFetcher = React.useMemo(() => {
        const loadData = (value) => {
          fetchRef.current += 1;
          const fetchId = fetchRef.current;
          setOptions([]);
          setFetching(true);
          
          try {
            const newOptions = fetch_ddl(value, ddl_type, queryParams);
            if (fetchId === fetchRef.current) {
              setOptions(newOptions);
              setFetching(false);
            }
          } catch (error) {
            console.error("Error fetching data:", error);
            setFetching(false);
          }
        };
    
        return setTimeout(loadData, debounceTimeout);
      }, [fetch_ddl, queryParams]);
    
    
      React.useEffect(() => {
        setOptions(fetch_ddl("", ddl_type, queryParams));
        setFetching(false);
      }, [queryParams, ddl_type, value]);
    
    
      React.useEffect(() => {
        setInternalValue(undefined);
      }, [queryParams]);
    
    
      const handleSelectChange = (value) => {
        if (onChange) onChange(value);
      };
    
      return (
        <antd.Select
          value={internalValue}
          labelInValue
          filterOption={false}
          onSearch={debounceFetcher}
          notFoundContent={fetching ? <antd.Spin size="small" /> : null}
          {...props}
          options={options}
          onChange={handleSelectChange}
        />
      );
    }
    
    { /* End: SearchableDropDown Component */}
    
    { /* Start: App Component */}
    
    function App() {
      const [form] = antd.Form.useForm(undefined);
      const [facility, setFacility] = React.useState("");
      const [specialization, setSpecialization] = React.useState("");
      const [doctor, setDoctor] = React.useState(undefined);
    
      const onFinish = (values) => {
        console.log("Received values of form: ", values);
      };
    
      const getParams = () => {
        var params = {};
    
        if (facility) params = { ...params, facilityId: facility };
        if (specialization)
          params = { ...params, specializationId: specialization };
    
        return params;
      };
    
    
      React.useEffect(() => {
        setDoctor(undefined);
        form.resetFields(["doctorId"]);
      }, [facility, specialization]);
    
    
      return (
        <div className="container mx-auto p-4">
          <antd.Form
            form={form}
            layout="vertical"
            onFinish={onFinish}
            className="bg-white p-6 rounded-lg shadow-md"
          >
            <h2 className="text-xl mb-4">Appointment Details</h2>
            
            <antd.Row gutter={16}>
              <antd.Col span={12}>
                <antd.Form.Item
                  name="facilityId"
                  label="Facility"
                  getValueFromEvent={(option) => option.value}
                  rules={[{ required: true, message: "Please select a facility" }]}
                >
                  <SearchableDropDown
                    allowClear={true}
                    onChange={(val) => {
                      setFacility(val.value);
                    }}
                    showSearch
                    style={{ width: "100%" }}
                    placeholder="Search by facility"
                    ddl_type={DDL_Type.Facility}
                    className="flex-1"
                  />
                </antd.Form.Item>
              </antd.Col>
              
              <antd.Col span={12}>
                <antd.Form.Item
                  name="specializationId"
                  label="Specialization"
                  getValueFromEvent={(option) => option.value}
                  rules={[
                    { required: true, message: "Please select a specialization" },
                  ]}
                >
                  <SearchableDropDown
                    onChange={(val) => {
                      setSpecialization(val.value);
                    }}
                    allowClear={true}
                    showSearch
                    style={{ width: "100%" }}
                    placeholder="Search by specialization"
                    ddl_type={DDL_Type.Specialization}
                    className="flex-1"
                  />
                </antd.Form.Item>
              </antd.Col>
            </antd.Row>
            
            <antd.Row gutter={16}>
              <antd.Col span={12}>
                <antd.Form.Item
                  name="doctorId"
                  label="Doctor"
                  getValueFromEvent={(option) => option.value}
                  rules={[{ required: true, message: "Please select a doctor" }]}
                >
                  <SearchableDropDown
                    value={doctor}
                    allowClear={true}
                    queryParams={getParams()}
                    //onChange={(val) => setDoctor(val)}
                    showSearch
                    style={{ width: "100%" }}
                    placeholder="Search by doctor"
                    ddl_type={DDL_Type.Doctor}
                    className="flex-1"
                    key={"ddl-" + facility + "-" + specialization}
                  />
                </antd.Form.Item>
              </antd.Col>
              
              <antd.Col span={12}>
                <antd.Form.Item
                  name="dateTime"
                  label="Appointment Date & Time"
                  rules={[
                    { required: true, message: "Please select a date and time" },
                  ]}
                >
                  <antd.DatePicker
                    showTime
                    minuteStep={15}
                    needConfirm={false}
                    use12Hours={true}
                    format="YYYY-MM-DD HH:mm"
                    className="w-full"
                    placeholder="Select Date & Time"
                  />
                </antd.Form.Item>
              </antd.Col>
            </antd.Row>
    
            <antd.Form.Item>
              <antd.Button type="default" htmlType="submit" className="w-full">
                Book Appointment
              </antd.Button>
            </antd.Form.Item>
          </antd.Form>
        </div>
      );
    }
    
    { /* End: App Component */}
    
    ReactDOM.render(<App />, document.getElementById("root"));
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/dayjs/1.11.13/dayjs.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/antd/5.20.2/antd.min.js"></script>
    <div id="root"></div>