Skip to content

Commit 144a0d4

Browse files
fix: restore onChange type inference for single date picker
Reverts the discriminated union changes from commit 3a82fd1 that made `selectsRange` and `selectsMultiple` optional in their respective union branches. This broke TypeScript's ability to infer the onChange parameter type when neither prop is specified. The fix restores required `selectsRange: true` and `selectsMultiple: true` in their respective union branches, allowing TypeScript to properly narrow the type and infer `Date | null` for the default single-date case. Users of range/multiple selection already pass these props explicitly (required for runtime behavior), so this change aligns types with actual usage patterns. Wrapper components that need to spread props can use type assertions as a workaround (see issue #6131 for details). Also adds explicit type inference tests to prevent regression. Fixes #6202 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 644f2f5 commit 144a0d4

6 files changed

Lines changed: 104 additions & 15 deletions

File tree

src/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,7 @@ export type DatePickerProps = OmitUnion<
258258
) => void;
259259
}
260260
| {
261-
selectsRange?: true;
261+
selectsRange: true;
262262
selectsMultiple?: false | undefined;
263263
formatMultipleDates?: never;
264264
onChange?: (
@@ -270,7 +270,7 @@ export type DatePickerProps = OmitUnion<
270270
}
271271
| {
272272
selectsRange?: false | undefined;
273-
selectsMultiple?: true;
273+
selectsMultiple: true;
274274
formatMultipleDates?: (
275275
dates: Date[],
276276
formatDate: (date: Date) => string,

src/test/calendar_test.test.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1373,7 +1373,7 @@ describe("Calendar", () => {
13731373
<DatePicker
13741374
selected={newDate("2017-07-28")}
13751375
adjustDateOnChange
1376-
onChange={(d: Date | null) => {
1376+
onChange={(d) => {
13771377
date = d;
13781378
}}
13791379
/>,
@@ -1397,7 +1397,7 @@ describe("Calendar", () => {
13971397
<DatePicker
13981398
selected={newDate("2017-07-28")}
13991399
adjustDateOnChange
1400-
onChange={(d: Date | null) => {
1400+
onChange={(d) => {
14011401
date = d;
14021402
}}
14031403
/>,
@@ -1421,7 +1421,7 @@ describe("Calendar", () => {
14211421
<DatePicker
14221422
selected={newDate("2017-12-31")}
14231423
adjustDateOnChange
1424-
onChange={(d: Date | null) => {
1424+
onChange={(d) => {
14251425
date = d;
14261426
}}
14271427
/>,

src/test/datepicker_test.test.tsx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1150,7 +1150,7 @@ describe("DatePicker", () => {
11501150
<DatePicker
11511151
inline
11521152
selected={selected}
1153-
onChange={(d: Date | null) => {
1153+
onChange={(d) => {
11541154
date = d;
11551155
}}
11561156
/>,
@@ -1172,7 +1172,7 @@ describe("DatePicker", () => {
11721172
const { container } = render(
11731173
<DatePicker
11741174
selected={selected}
1175-
onChange={(d: Date | null) => {
1175+
onChange={(d) => {
11761176
date = d;
11771177
}}
11781178
/>,
@@ -4610,7 +4610,7 @@ describe("DatePicker", () => {
46104610
const { container } = render(
46114611
<DatePicker
46124612
selected={selected}
4613-
onChange={(d: Date | null) => {
4613+
onChange={(d) => {
46144614
date = d;
46154615
}}
46164616
showTimeSelect
@@ -4641,7 +4641,7 @@ describe("DatePicker", () => {
46414641
const { container: datepicker } = render(
46424642
<DatePicker
46434643
selected={selected}
4644-
onChange={(d: Date | null) => {
4644+
onChange={(d) => {
46454645
date = d;
46464646
}}
46474647
showTimeSelect
@@ -4667,7 +4667,7 @@ describe("DatePicker", () => {
46674667
const { container: datepicker } = render(
46684668
<DatePicker
46694669
selected={selected}
4670-
onChange={(d: Date | null) => {
4670+
onChange={(d) => {
46714671
date = d;
46724672
}}
46734673
showTimeSelectOnly
@@ -4691,7 +4691,7 @@ describe("DatePicker", () => {
46914691
const { container } = render(
46924692
<DatePicker
46934693
selected={selected}
4694-
onChange={(d: Date | null) => (date = d)}
4694+
onChange={(d) => (date = d)}
46954695
dateFormat="MM/yyyy"
46964696
minDate={newDate("2022-12-31")}
46974697
showMonthYearPicker
@@ -4720,7 +4720,7 @@ describe("DatePicker", () => {
47204720
const { container } = render(
47214721
<DatePicker
47224722
selected={selected}
4723-
onChange={(d: Date | null) => (date = d)}
4723+
onChange={(d) => (date = d)}
47244724
dateFormat="yyyy"
47254725
minDate={newDate("2022-12-31")}
47264726
showYearPicker

src/test/timezone_test.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,7 @@ describe("DatePicker with timeZone prop", () => {
303303
const { container } = render(
304304
<DatePicker
305305
selected={utcDate}
306-
onChange={(date: Date | null) => {
306+
onChange={(date) => {
307307
selectedDate = date;
308308
}}
309309
timeZone="America/New_York"
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/**
2+
* Type inference tests for DatePicker onChange callback
3+
*
4+
* These tests verify that TypeScript can properly infer the type of the
5+
* onChange callback parameter without explicit type annotations.
6+
*
7+
* Related issues:
8+
* - #6202: onChange type breaks after updating to 9.1.0
9+
* - #6131: selectsMultiple prop type incompatibility when spreading props
10+
*/
11+
import React from "react";
12+
import { render, fireEvent } from "@testing-library/react";
13+
14+
import DatePicker from "../index";
15+
import { safeQuerySelector } from "./test_utils";
16+
17+
describe("DatePicker onChange type inference", () => {
18+
it("should infer Date | null for single date picker without explicit type annotation", () => {
19+
// This test verifies fix for issue #6202
20+
// If TypeScript cannot infer the type, this test will fail to compile
21+
let selectedDate: Date | null = null;
22+
23+
const { container } = render(
24+
<DatePicker
25+
selected={selectedDate}
26+
// The key test: NO explicit type annotation on `date` parameter
27+
// TypeScript should infer `date` as `Date | null`
28+
onChange={(date) => {
29+
// If type inference works, this assignment should compile without error
30+
selectedDate = date;
31+
}}
32+
/>,
33+
);
34+
35+
const input = safeQuerySelector<HTMLInputElement>(container, "input");
36+
fireEvent.change(input, { target: { value: "01/01/2024" } });
37+
38+
expect(selectedDate).not.toBeNull();
39+
});
40+
41+
it("should infer [Date | null, Date | null] for range picker without explicit type annotation", () => {
42+
let startDate: Date | null = null;
43+
let endDate: Date | null = null;
44+
45+
const { container } = render(
46+
<DatePicker
47+
selectsRange={true}
48+
startDate={startDate}
49+
endDate={endDate}
50+
// The key test: NO explicit type annotation on `dates` parameter
51+
// TypeScript should infer `dates` as `[Date | null, Date | null]`
52+
onChange={(dates) => {
53+
// If type inference works, destructuring should work without error
54+
const [start, end] = dates;
55+
startDate = start;
56+
endDate = end;
57+
}}
58+
/>,
59+
);
60+
61+
const input = safeQuerySelector<HTMLInputElement>(container, "input");
62+
fireEvent.change(input, { target: { value: "01/01/2024" } });
63+
64+
expect(startDate).not.toBeNull();
65+
});
66+
67+
it("should infer Date[] | null for multiple picker without explicit type annotation", () => {
68+
let selectedDates: Date[] | null = null;
69+
70+
const { container } = render(
71+
<DatePicker
72+
selectsMultiple={true}
73+
selectedDates={selectedDates ?? []}
74+
// The key test: NO explicit type annotation on `dates` parameter
75+
// TypeScript should infer `dates` as `Date[] | null`
76+
onChange={(dates) => {
77+
// If type inference works, this assignment should compile without error
78+
selectedDates = dates;
79+
}}
80+
/>,
81+
);
82+
83+
const input = safeQuerySelector<HTMLInputElement>(container, "input");
84+
fireEvent.change(input, { target: { value: "01/01/2024" } });
85+
86+
// Multiple picker doesn't respond to text input the same way, just verify render
87+
expect(container.querySelector("input")).toBeTruthy();
88+
});
89+
});

src/test/year_picker_test.test.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -642,7 +642,7 @@ describe("YearPicker", () => {
642642
selected={newDate("2020-01-01")}
643643
adjustDateOnChange
644644
showYearPicker
645-
onChange={(d: Date | null) => {
645+
onChange={(d) => {
646646
date = d;
647647
}}
648648
/>,
@@ -674,7 +674,7 @@ describe("YearPicker", () => {
674674
selected={newDate("2020-01-01")}
675675
adjustDateOnChange
676676
showYearPicker
677-
onChange={(d: Date | null) => {
677+
onChange={(d) => {
678678
date = d;
679679
}}
680680
/>,

0 commit comments

Comments
 (0)