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:
undefined
or null
.key
prop to the SearchableDropDown
to force
re-rendering.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!
In your app/page.tsx
file, you forgot to add form={form}
attribute in your antd
Form
Tag.
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
libdebounce
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>