From 144a0d46bfedc21c269afadc6ac2d63c122e28b4 Mon Sep 17 00:00:00 2001 From: Martijn Russchen Date: Mon, 5 Jan 2026 10:55:56 +0100 Subject: [PATCH] fix: restore onChange type inference for single date picker MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reverts the discriminated union changes from commit 3a82fd16 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 --- src/index.tsx | 4 +- src/test/calendar_test.test.tsx | 6 +- src/test/datepicker_test.test.tsx | 14 ++--- src/test/timezone_test.test.tsx | 2 +- src/test/type_inference_test.test.tsx | 89 +++++++++++++++++++++++++++ src/test/year_picker_test.test.tsx | 4 +- 6 files changed, 104 insertions(+), 15 deletions(-) create mode 100644 src/test/type_inference_test.test.tsx diff --git a/src/index.tsx b/src/index.tsx index f991ac639f..415b225087 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -258,7 +258,7 @@ export type DatePickerProps = OmitUnion< ) => void; } | { - selectsRange?: true; + selectsRange: true; selectsMultiple?: false | undefined; formatMultipleDates?: never; onChange?: ( @@ -270,7 +270,7 @@ export type DatePickerProps = OmitUnion< } | { selectsRange?: false | undefined; - selectsMultiple?: true; + selectsMultiple: true; formatMultipleDates?: ( dates: Date[], formatDate: (date: Date) => string, diff --git a/src/test/calendar_test.test.tsx b/src/test/calendar_test.test.tsx index 7e789a3720..721ee05232 100644 --- a/src/test/calendar_test.test.tsx +++ b/src/test/calendar_test.test.tsx @@ -1373,7 +1373,7 @@ describe("Calendar", () => { { + onChange={(d) => { date = d; }} />, @@ -1397,7 +1397,7 @@ describe("Calendar", () => { { + onChange={(d) => { date = d; }} />, @@ -1421,7 +1421,7 @@ describe("Calendar", () => { { + onChange={(d) => { date = d; }} />, diff --git a/src/test/datepicker_test.test.tsx b/src/test/datepicker_test.test.tsx index dd449021fe..31b239b375 100644 --- a/src/test/datepicker_test.test.tsx +++ b/src/test/datepicker_test.test.tsx @@ -1150,7 +1150,7 @@ describe("DatePicker", () => { { + onChange={(d) => { date = d; }} />, @@ -1172,7 +1172,7 @@ describe("DatePicker", () => { const { container } = render( { + onChange={(d) => { date = d; }} />, @@ -4610,7 +4610,7 @@ describe("DatePicker", () => { const { container } = render( { + onChange={(d) => { date = d; }} showTimeSelect @@ -4641,7 +4641,7 @@ describe("DatePicker", () => { const { container: datepicker } = render( { + onChange={(d) => { date = d; }} showTimeSelect @@ -4667,7 +4667,7 @@ describe("DatePicker", () => { const { container: datepicker } = render( { + onChange={(d) => { date = d; }} showTimeSelectOnly @@ -4691,7 +4691,7 @@ describe("DatePicker", () => { const { container } = render( (date = d)} + onChange={(d) => (date = d)} dateFormat="MM/yyyy" minDate={newDate("2022-12-31")} showMonthYearPicker @@ -4720,7 +4720,7 @@ describe("DatePicker", () => { const { container } = render( (date = d)} + onChange={(d) => (date = d)} dateFormat="yyyy" minDate={newDate("2022-12-31")} showYearPicker diff --git a/src/test/timezone_test.test.tsx b/src/test/timezone_test.test.tsx index c9819f15d1..4968057ead 100644 --- a/src/test/timezone_test.test.tsx +++ b/src/test/timezone_test.test.tsx @@ -303,7 +303,7 @@ describe("DatePicker with timeZone prop", () => { const { container } = render( { + onChange={(date) => { selectedDate = date; }} timeZone="America/New_York" diff --git a/src/test/type_inference_test.test.tsx b/src/test/type_inference_test.test.tsx new file mode 100644 index 0000000000..16c73659a1 --- /dev/null +++ b/src/test/type_inference_test.test.tsx @@ -0,0 +1,89 @@ +/** + * Type inference tests for DatePicker onChange callback + * + * These tests verify that TypeScript can properly infer the type of the + * onChange callback parameter without explicit type annotations. + * + * Related issues: + * - #6202: onChange type breaks after updating to 9.1.0 + * - #6131: selectsMultiple prop type incompatibility when spreading props + */ +import React from "react"; +import { render, fireEvent } from "@testing-library/react"; + +import DatePicker from "../index"; +import { safeQuerySelector } from "./test_utils"; + +describe("DatePicker onChange type inference", () => { + it("should infer Date | null for single date picker without explicit type annotation", () => { + // This test verifies fix for issue #6202 + // If TypeScript cannot infer the type, this test will fail to compile + let selectedDate: Date | null = null; + + const { container } = render( + { + // If type inference works, this assignment should compile without error + selectedDate = date; + }} + />, + ); + + const input = safeQuerySelector(container, "input"); + fireEvent.change(input, { target: { value: "01/01/2024" } }); + + expect(selectedDate).not.toBeNull(); + }); + + it("should infer [Date | null, Date | null] for range picker without explicit type annotation", () => { + let startDate: Date | null = null; + let endDate: Date | null = null; + + const { container } = render( + { + // If type inference works, destructuring should work without error + const [start, end] = dates; + startDate = start; + endDate = end; + }} + />, + ); + + const input = safeQuerySelector(container, "input"); + fireEvent.change(input, { target: { value: "01/01/2024" } }); + + expect(startDate).not.toBeNull(); + }); + + it("should infer Date[] | null for multiple picker without explicit type annotation", () => { + let selectedDates: Date[] | null = null; + + const { container } = render( + { + // If type inference works, this assignment should compile without error + selectedDates = dates; + }} + />, + ); + + const input = safeQuerySelector(container, "input"); + fireEvent.change(input, { target: { value: "01/01/2024" } }); + + // Multiple picker doesn't respond to text input the same way, just verify render + expect(container.querySelector("input")).toBeTruthy(); + }); +}); diff --git a/src/test/year_picker_test.test.tsx b/src/test/year_picker_test.test.tsx index 995ad2858f..6721abcd2f 100644 --- a/src/test/year_picker_test.test.tsx +++ b/src/test/year_picker_test.test.tsx @@ -642,7 +642,7 @@ describe("YearPicker", () => { selected={newDate("2020-01-01")} adjustDateOnChange showYearPicker - onChange={(d: Date | null) => { + onChange={(d) => { date = d; }} />, @@ -674,7 +674,7 @@ describe("YearPicker", () => { selected={newDate("2020-01-01")} adjustDateOnChange showYearPicker - onChange={(d: Date | null) => { + onChange={(d) => { date = d; }} />,