Skip to content

Commit 9b81282

Browse files
Merge pull request #5782 from qburst/issue-5759/fix/null-start-date-range
🐛 Refactor DatePicker component to handle null startDate date range selection properly
2 parents 35404bf + cf2956b commit 9b81282

File tree

4 files changed

+136
-8
lines changed

4 files changed

+136
-8
lines changed

src/date_utils.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -245,11 +245,11 @@ export function safeDateRangeFormat(
245245
rangeSeparator?: string;
246246
},
247247
): string {
248-
if (!startDate) {
248+
if (!startDate && !endDate) {
249249
return "";
250250
}
251251

252-
const formattedStartDate = safeDateFormat(startDate, props);
252+
const formattedStartDate = startDate ? safeDateFormat(startDate, props) : "";
253253
const formattedEndDate = endDate ? safeDateFormat(endDate, props) : "";
254254
const dateRangeSeparator = props.rangeSeparator || DATE_RANGE_SEPARATOR;
255255

src/index.tsx

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -670,12 +670,14 @@ export class DatePicker extends Component<DatePickerProps, DatePickerState> {
670670
this.props.locale,
671671
strictParsing,
672672
);
673-
const endDateNew = parseDate(
674-
valueEnd ?? "",
675-
dateFormat,
676-
this.props.locale,
677-
strictParsing,
678-
);
673+
const endDateNew = startDateNew
674+
? parseDate(
675+
valueEnd ?? "",
676+
dateFormat,
677+
this.props.locale,
678+
strictParsing,
679+
)
680+
: null;
679681
const startChanged = startDate?.getTime() !== startDateNew?.getTime();
680682
const endChanged = endDate?.getTime() !== endDateNew?.getTime();
681683

@@ -832,6 +834,7 @@ export class DatePicker extends Component<DatePickerProps, DatePickerState> {
832834
if (selectsRange) {
833835
const noRanges = !startDate && !endDate;
834836
const hasStartRange = startDate && !endDate;
837+
const hasOnlyEndRange = !startDate && !!endDate;
835838
const isRangeFilled = startDate && endDate;
836839
if (noRanges) {
837840
onChange?.([changedDate, null], event);
@@ -847,6 +850,12 @@ export class DatePicker extends Component<DatePickerProps, DatePickerState> {
847850
} else {
848851
onChange?.([startDate, changedDate], event);
849852
}
853+
} else if (hasOnlyEndRange) {
854+
if (changedDate && isDateBefore(changedDate, endDate)) {
855+
onChange?.([changedDate, endDate], event);
856+
} else {
857+
onChange?.([changedDate, null], event);
858+
}
850859
}
851860
if (isRangeFilled) {
852861
onChange?.([changedDate, null], event);

src/test/date_utils_test.test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1264,6 +1264,14 @@ describe("date_utils", () => {
12641264
);
12651265
});
12661266

1267+
it("should return a formatted endDate prefixed by a dash when startDate is null", () => {
1268+
const startDate = null;
1269+
const endDate = new Date("2021-04-20 00:00:00");
1270+
expect(safeDateRangeFormat(startDate, endDate, props)).toBe(
1271+
" - 04/20/2021",
1272+
);
1273+
});
1274+
12671275
it("should return a formatted startDate followed by a dash followed by a formatted endDate when startDate and endDate both have values", () => {
12681276
const startDate = new Date("2021-04-20 00:00:00");
12691277
const endDate = new Date("2021-04-28 00:00:00");

src/test/datepicker_test.test.tsx

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4531,4 +4531,115 @@ describe("DatePicker", () => {
45314531
expect(input?.value).toBe("07/17/2025");
45324532
});
45334533
});
4534+
4535+
describe("Date Range - Handle null start date", () => {
4536+
it("should display the endDate when the startDate is not available", () => {
4537+
const endDateLabel = "2025-11-22";
4538+
const { container } = render(
4539+
<DatePicker
4540+
selectsRange
4541+
startDate={null}
4542+
endDate={newDate(endDateLabel)}
4543+
dateFormat="yyyy-MM-dd"
4544+
onChange={() => {}}
4545+
isClearable
4546+
/>,
4547+
);
4548+
const input = safeQuerySelector<HTMLInputElement>(container, "input");
4549+
expect(input.value).toBe(` - ${endDateLabel}`);
4550+
});
4551+
4552+
it("should clear the input when the startDate alone is cleared while the endDate is still available", () => {
4553+
const startDateLabel = "2025-11-17";
4554+
const endDateLabel = "2025-11-22";
4555+
const onChangeSpy = jest.fn();
4556+
4557+
const { container } = render(
4558+
<DatePicker
4559+
selectsRange
4560+
startDate={newDate(startDateLabel)}
4561+
endDate={newDate(endDateLabel)}
4562+
dateFormat="yyyy-MM-dd"
4563+
onChange={onChangeSpy}
4564+
isClearable
4565+
/>,
4566+
);
4567+
const input = safeQuerySelector<HTMLInputElement>(container, "input");
4568+
expect(input.value).toBe(`${startDateLabel} - ${endDateLabel}`);
4569+
4570+
fireEvent.change(input, { target: { value: ` - ${endDateLabel}` } });
4571+
expect(onChangeSpy).toHaveBeenCalledTimes(1);
4572+
expect(onChangeSpy).toHaveBeenCalledWith([null, null], expect.anything());
4573+
});
4574+
4575+
it("should clear the endDate and set the startDate when the endDate is alone available and the newly selected startDate is greater than the endDate", () => {
4576+
const endDateLabel = "2025-11-20";
4577+
const onChangeSpy = jest.fn();
4578+
const { container } = render(
4579+
<DatePicker
4580+
selectsRange
4581+
startDate={null}
4582+
selected={newDate(endDateLabel)}
4583+
endDate={newDate(endDateLabel)}
4584+
dateFormat="yyyy-MM-dd"
4585+
onChange={onChangeSpy}
4586+
isClearable
4587+
/>,
4588+
);
4589+
const input = safeQuerySelector<HTMLInputElement>(container, "input");
4590+
fireEvent.focus(input);
4591+
4592+
expect(container.querySelector(".react-datepicker")).toBeTruthy();
4593+
const newStartDateEl = safeQuerySelector(
4594+
container,
4595+
".react-datepicker__day--021",
4596+
);
4597+
fireEvent.click(newStartDateEl);
4598+
4599+
expect(onChangeSpy).toHaveBeenCalledTimes(1);
4600+
const changedDateRange = onChangeSpy.mock.calls[0][0];
4601+
const [changedStartDate, changedEndDate] = changedDateRange;
4602+
4603+
expect(changedEndDate).toBe(null);
4604+
expect(changedStartDate.toISOString()).toBe(
4605+
newDate("2025-11-21").toISOString(),
4606+
);
4607+
});
4608+
4609+
it("should set the startDate when the endDate is alone available and the newly selected startDate is less than the endDate", () => {
4610+
const endDateLabel = "2025-11-20";
4611+
const onChangeSpy = jest.fn();
4612+
const { container } = render(
4613+
<DatePicker
4614+
selectsRange
4615+
startDate={null}
4616+
selected={newDate(endDateLabel)}
4617+
endDate={newDate(endDateLabel)}
4618+
dateFormat="yyyy-MM-dd"
4619+
onChange={onChangeSpy}
4620+
isClearable
4621+
/>,
4622+
);
4623+
const input = safeQuerySelector<HTMLInputElement>(container, "input");
4624+
fireEvent.focus(input);
4625+
4626+
expect(container.querySelector(".react-datepicker")).toBeTruthy();
4627+
const newStartDateEl = safeQuerySelector(
4628+
container,
4629+
".react-datepicker__day--019",
4630+
);
4631+
fireEvent.click(newStartDateEl);
4632+
4633+
expect(onChangeSpy).toHaveBeenCalledTimes(1);
4634+
const changedDateRange = onChangeSpy.mock.calls[0][0];
4635+
const [changedStartDate, changedEndDate] = changedDateRange;
4636+
4637+
expect(changedEndDate.toISOString()).toBe(
4638+
newDate(endDateLabel).toISOString(),
4639+
);
4640+
expect(changedStartDate.toISOString()).toBe(
4641+
newDate("2025-11-19").toISOString(),
4642+
);
4643+
});
4644+
});
45344645
});

0 commit comments

Comments
 (0)