React state management hooks for date picker components with internationalization and accessibility support.
—
State management for date range picker components that allow users to select a start and end date. Combines two date fields with a range calendar popover to enable comprehensive date range selection with validation and formatting.
Creates a state object for managing date range picker component state including start/end date values, overlay visibility, and range validation.
/**
* Provides state management for a date range picker component.
* A date range picker combines two DateFields and a RangeCalendar popover to allow
* users to enter or select a date and time range.
* @param props - Configuration options for the date range picker state
* @returns DateRangePickerState object with range management and overlay controls
*/
function useDateRangePickerState<T extends DateValue = DateValue>(
props: DateRangePickerStateOptions<T>
): DateRangePickerState;
interface DateRangePickerStateOptions<T extends DateValue = DateValue> extends DateRangePickerProps<T> {
/**
* Determines whether the date picker popover should close automatically when a date range is selected.
* @default true
*/
shouldCloseOnSelect?: boolean | (() => boolean);
}
interface DateRangePickerState extends OverlayTriggerState, FormValidationState {
/** The currently selected date range. */
value: RangeValue<DateValue | null>;
/** The default selected date range. */
defaultValue: DateRange | null;
/** Sets the selected date range. */
setValue(value: DateRange | null): void;
/** The date portion of the selected range. This may be set prior to `value` if the user has selected a date range but has not yet selected a time range. */
dateRange: RangeValue<DateValue | null> | null;
/** Sets the date portion of the selected range. */
setDateRange(value: DateRange): void;
/** The time portion of the selected range. This may be set prior to `value` if the user has selected a time range but has not yet selected a date range. */
timeRange: RangeValue<TimeValue | null> | null;
/** Sets the time portion of the selected range. */
setTimeRange(value: RangeValue<TimeValue | null>): void;
/** Sets the date portion of either the start or end of the selected range. */
setDate(part: 'start' | 'end', value: DateValue | null): void;
/** Sets the time portion of either the start or end of the selected range. */
setTime(part: 'start' | 'end', value: TimeValue | null): void;
/** Sets the date and time of either the start or end of the selected range. */
setDateTime(part: 'start' | 'end', value: DateValue | null): void;
/** The granularity for the field, based on the `granularity` prop and current value. */
granularity: Granularity;
/** Whether the date range picker supports selecting times, according to the `granularity` prop and current value. */
hasTime: boolean;
/** Whether the calendar popover is currently open. */
isOpen: boolean;
/** Sets whether the calendar popover is open. */
setOpen(isOpen: boolean): void;
/** The current validation state of the date range picker, based on the `validationState`, `minValue`, and `maxValue` props. @deprecated Use `isInvalid` instead. */
validationState: ValidationState | null;
/** Whether the date range picker is invalid, based on the `isInvalid`, `minValue`, and `maxValue` props. */
isInvalid: boolean;
/** Formats the selected range using the given options. */
formatValue(locale: string, fieldOptions: FieldOptions): {start: string, end: string} | null;
/** Gets a formatter based on state's props. */
getDateFormatter(locale: string, formatOptions: FormatterOptions): DateFormatter;
}Usage Examples:
import { useDateRangePickerState } from "@react-stately/datepicker";
import { CalendarDate, CalendarDateTime } from "@internationalized/date";
// Basic date range picker
function BasicDateRangePicker() {
const state = useDateRangePickerState({
defaultValue: {
start: new CalendarDate(2023, 6, 1),
end: new CalendarDate(2023, 6, 15)
},
onChange: (range) => {
if (range) {
console.log("Range:", range.start.toString(), "to", range.end.toString());
}
}
});
const formatValue = state.formatValue('en-US', {});
return (
<div>
<div>
<input
value={formatValue?.start || "Start date"}
readOnly
onClick={() => state.setOpen(true)}
/>
<span> to </span>
<input
value={formatValue?.end || "End date"}
readOnly
onClick={() => state.setOpen(true)}
/>
</div>
{state.isOpen && <div>Range calendar popover</div>}
</div>
);
}
// Date and time range picker
function DateTimeRangePicker() {
const state = useDateRangePickerState({
granularity: 'minute',
shouldCloseOnSelect: false, // Keep open for time selection
onChange: (range) => {
if (range?.start && range?.end) {
console.log("Start:", range.start.toDate('UTC'));
console.log("End:", range.end.toDate('UTC'));
}
}
});
return (
<div>
<button onClick={() => state.setOpen(!state.isOpen)}>
{state.value?.start && state.value?.end
? `${state.value.start.toString()} - ${state.value.end.toString()}`
: "Select date range"
}
</button>
{state.hasTime && <span>Includes time selection</span>}
{state.isOpen && (
<div>
<div>Range calendar with time selectors</div>
<button onClick={() => state.setOpen(false)}>Close</button>
</div>
)}
</div>
);
}
// Controlled range picker with individual date/time setting
function ControlledRangePicker({ value, onChange }) {
const state = useDateRangePickerState({
value,
onChange,
granularity: 'hour',
minValue: new CalendarDateTime(2023, 1, 1, 0),
maxValue: new CalendarDateTime(2024, 12, 31, 23),
startName: 'startDate',
endName: 'endDate'
});
const handleStartDateChange = (date: CalendarDate) => {
state.setDate('start', date);
};
const handleEndTimeChange = (time: Time) => {
state.setTime('end', time);
};
return (
<div>
<div style={{ border: state.isInvalid ? '2px solid red' : '1px solid #ccc' }}>
Range: {state.value?.start?.toString()} to {state.value?.end?.toString()}
</div>
<div>
<button onClick={() => handleStartDateChange(new CalendarDate(2023, 6, 1))}>
Set Start Date
</button>
<button onClick={() => handleEndTimeChange(new Time(18, 0))}>
Set End Time to 6 PM
</button>
</div>
{state.dateRange && !state.value?.start && (
<div>Date range selected, awaiting time selection</div>
)}
</div>
);
}The date range picker state manages separate date and time ranges that are combined into the final value.
/**
* Sets the date portion of the selected range
* @param value - The date range to set
*/
setDateRange(value: DateRange): void;
/**
* Sets the time portion of the selected range
* @param value - The time range to set
*/
setTimeRange(value: RangeValue<TimeValue | null>): void;
/**
* Sets the date portion of either start or end
* @param part - Whether to set 'start' or 'end' date
* @param value - The date value to set
*/
setDate(part: 'start' | 'end', value: DateValue | null): void;
/**
* Sets the time portion of either start or end
* @param part - Whether to set 'start' or 'end' time
* @param value - The time value to set
*/
setTime(part: 'start' | 'end', value: TimeValue | null): void;
/**
* Sets both date and time for either start or end
* @param part - Whether to set 'start' or 'end' datetime
* @param value - The datetime value to set
*/
setDateTime(part: 'start' | 'end', value: DateValue | null): void;Provides intelligent formatting that can optimize shared date parts between start and end dates.
/**
* Formats the selected range using the given options
* Automatically optimizes formatting when start and end share common parts (e.g., same month)
* @param locale - The locale to format in (e.g. 'en-US', 'fr-FR')
* @param fieldOptions - Formatting options for different date/time parts
* @returns Object with formatted start and end strings, or null if no complete range
*/
formatValue(locale: string, fieldOptions: FieldOptions): {start: string, end: string} | null;Example formatting behavior:
// Same month: "June 1 - 15, 2023"
// Different months: "June 1 - July 15, 2023"
// Different years: "Dec 31, 2023 - Jan 1, 2024"interface RangeValue<T> {
start: T;
end: T;
}
type DateRange = RangeValue<DateValue>;
type TimeRange = RangeValue<TimeValue>;Range validation includes all standard date validation plus range-specific validation.
interface DateRangePickerState {
/** Whether the date range picker is invalid, based on validation props and range logic */
isInvalid: boolean;
/** The current validation state @deprecated Use `isInvalid` instead */
validationState: ValidationState | null;
}Range-specific validation errors:
Supports HTML forms with separate names for start and end dates.
interface DateRangePickerProps<T> {
/** The name of the start date input element for HTML forms */
startName?: string;
/** The name of the end date input element for HTML forms */
endName?: string;
/** Whether user input is required before form submission */
isRequired?: boolean;
}Example with form submission:
const state = useDateRangePickerState({
startName: 'trip_start',
endName: 'trip_end',
isRequired: true,
onChange: (range) => {
// Form will include trip_start and trip_end fields
}
});Install with Tessl CLI
npx tessl i tessl/npm-react-stately--datepicker