0
# Date Range Picker State
1
2
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.
3
4
## Capabilities
5
6
### useDateRangePickerState Hook
7
8
Creates a state object for managing date range picker component state including start/end date values, overlay visibility, and range validation.
9
10
```typescript { .api }
11
/**
12
* Provides state management for a date range picker component.
13
* A date range picker combines two DateFields and a RangeCalendar popover to allow
14
* users to enter or select a date and time range.
15
* @param props - Configuration options for the date range picker state
16
* @returns DateRangePickerState object with range management and overlay controls
17
*/
18
function useDateRangePickerState<T extends DateValue = DateValue>(
19
props: DateRangePickerStateOptions<T>
20
): DateRangePickerState;
21
22
interface DateRangePickerStateOptions<T extends DateValue = DateValue> extends DateRangePickerProps<T> {
23
/**
24
* Determines whether the date picker popover should close automatically when a date range is selected.
25
* @default true
26
*/
27
shouldCloseOnSelect?: boolean | (() => boolean);
28
}
29
30
interface DateRangePickerState extends OverlayTriggerState, FormValidationState {
31
/** The currently selected date range. */
32
value: RangeValue<DateValue | null>;
33
/** The default selected date range. */
34
defaultValue: DateRange | null;
35
/** Sets the selected date range. */
36
setValue(value: DateRange | null): void;
37
/** 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. */
38
dateRange: RangeValue<DateValue | null> | null;
39
/** Sets the date portion of the selected range. */
40
setDateRange(value: DateRange): void;
41
/** 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. */
42
timeRange: RangeValue<TimeValue | null> | null;
43
/** Sets the time portion of the selected range. */
44
setTimeRange(value: RangeValue<TimeValue | null>): void;
45
/** Sets the date portion of either the start or end of the selected range. */
46
setDate(part: 'start' | 'end', value: DateValue | null): void;
47
/** Sets the time portion of either the start or end of the selected range. */
48
setTime(part: 'start' | 'end', value: TimeValue | null): void;
49
/** Sets the date and time of either the start or end of the selected range. */
50
setDateTime(part: 'start' | 'end', value: DateValue | null): void;
51
/** The granularity for the field, based on the `granularity` prop and current value. */
52
granularity: Granularity;
53
/** Whether the date range picker supports selecting times, according to the `granularity` prop and current value. */
54
hasTime: boolean;
55
/** Whether the calendar popover is currently open. */
56
isOpen: boolean;
57
/** Sets whether the calendar popover is open. */
58
setOpen(isOpen: boolean): void;
59
/** The current validation state of the date range picker, based on the `validationState`, `minValue`, and `maxValue` props. @deprecated Use `isInvalid` instead. */
60
validationState: ValidationState | null;
61
/** Whether the date range picker is invalid, based on the `isInvalid`, `minValue`, and `maxValue` props. */
62
isInvalid: boolean;
63
/** Formats the selected range using the given options. */
64
formatValue(locale: string, fieldOptions: FieldOptions): {start: string, end: string} | null;
65
/** Gets a formatter based on state's props. */
66
getDateFormatter(locale: string, formatOptions: FormatterOptions): DateFormatter;
67
}
68
```
69
70
**Usage Examples:**
71
72
```typescript
73
import { useDateRangePickerState } from "@react-stately/datepicker";
74
import { CalendarDate, CalendarDateTime } from "@internationalized/date";
75
76
// Basic date range picker
77
function BasicDateRangePicker() {
78
const state = useDateRangePickerState({
79
defaultValue: {
80
start: new CalendarDate(2023, 6, 1),
81
end: new CalendarDate(2023, 6, 15)
82
},
83
onChange: (range) => {
84
if (range) {
85
console.log("Range:", range.start.toString(), "to", range.end.toString());
86
}
87
}
88
});
89
90
const formatValue = state.formatValue('en-US', {});
91
92
return (
93
<div>
94
<div>
95
<input
96
value={formatValue?.start || "Start date"}
97
readOnly
98
onClick={() => state.setOpen(true)}
99
/>
100
<span> to </span>
101
<input
102
value={formatValue?.end || "End date"}
103
readOnly
104
onClick={() => state.setOpen(true)}
105
/>
106
</div>
107
{state.isOpen && <div>Range calendar popover</div>}
108
</div>
109
);
110
}
111
112
// Date and time range picker
113
function DateTimeRangePicker() {
114
const state = useDateRangePickerState({
115
granularity: 'minute',
116
shouldCloseOnSelect: false, // Keep open for time selection
117
onChange: (range) => {
118
if (range?.start && range?.end) {
119
console.log("Start:", range.start.toDate('UTC'));
120
console.log("End:", range.end.toDate('UTC'));
121
}
122
}
123
});
124
125
return (
126
<div>
127
<button onClick={() => state.setOpen(!state.isOpen)}>
128
{state.value?.start && state.value?.end
129
? `${state.value.start.toString()} - ${state.value.end.toString()}`
130
: "Select date range"
131
}
132
</button>
133
134
{state.hasTime && <span>Includes time selection</span>}
135
136
{state.isOpen && (
137
<div>
138
<div>Range calendar with time selectors</div>
139
<button onClick={() => state.setOpen(false)}>Close</button>
140
</div>
141
)}
142
</div>
143
);
144
}
145
146
// Controlled range picker with individual date/time setting
147
function ControlledRangePicker({ value, onChange }) {
148
const state = useDateRangePickerState({
149
value,
150
onChange,
151
granularity: 'hour',
152
minValue: new CalendarDateTime(2023, 1, 1, 0),
153
maxValue: new CalendarDateTime(2024, 12, 31, 23),
154
startName: 'startDate',
155
endName: 'endDate'
156
});
157
158
const handleStartDateChange = (date: CalendarDate) => {
159
state.setDate('start', date);
160
};
161
162
const handleEndTimeChange = (time: Time) => {
163
state.setTime('end', time);
164
};
165
166
return (
167
<div>
168
<div style={{ border: state.isInvalid ? '2px solid red' : '1px solid #ccc' }}>
169
Range: {state.value?.start?.toString()} to {state.value?.end?.toString()}
170
</div>
171
172
<div>
173
<button onClick={() => handleStartDateChange(new CalendarDate(2023, 6, 1))}>
174
Set Start Date
175
</button>
176
<button onClick={() => handleEndTimeChange(new Time(18, 0))}>
177
Set End Time to 6 PM
178
</button>
179
</div>
180
181
{state.dateRange && !state.value?.start && (
182
<div>Date range selected, awaiting time selection</div>
183
)}
184
</div>
185
);
186
}
187
```
188
189
### Range Value Management
190
191
The date range picker state manages separate date and time ranges that are combined into the final value.
192
193
```typescript { .api }
194
/**
195
* Sets the date portion of the selected range
196
* @param value - The date range to set
197
*/
198
setDateRange(value: DateRange): void;
199
200
/**
201
* Sets the time portion of the selected range
202
* @param value - The time range to set
203
*/
204
setTimeRange(value: RangeValue<TimeValue | null>): void;
205
206
/**
207
* Sets the date portion of either start or end
208
* @param part - Whether to set 'start' or 'end' date
209
* @param value - The date value to set
210
*/
211
setDate(part: 'start' | 'end', value: DateValue | null): void;
212
213
/**
214
* Sets the time portion of either start or end
215
* @param part - Whether to set 'start' or 'end' time
216
* @param value - The time value to set
217
*/
218
setTime(part: 'start' | 'end', value: TimeValue | null): void;
219
220
/**
221
* Sets both date and time for either start or end
222
* @param part - Whether to set 'start' or 'end' datetime
223
* @param value - The datetime value to set
224
*/
225
setDateTime(part: 'start' | 'end', value: DateValue | null): void;
226
```
227
228
### Range Formatting
229
230
Provides intelligent formatting that can optimize shared date parts between start and end dates.
231
232
```typescript { .api }
233
/**
234
* Formats the selected range using the given options
235
* Automatically optimizes formatting when start and end share common parts (e.g., same month)
236
* @param locale - The locale to format in (e.g. 'en-US', 'fr-FR')
237
* @param fieldOptions - Formatting options for different date/time parts
238
* @returns Object with formatted start and end strings, or null if no complete range
239
*/
240
formatValue(locale: string, fieldOptions: FieldOptions): {start: string, end: string} | null;
241
```
242
243
Example formatting behavior:
244
```typescript
245
// Same month: "June 1 - 15, 2023"
246
// Different months: "June 1 - July 15, 2023"
247
// Different years: "Dec 31, 2023 - Jan 1, 2024"
248
```
249
250
### Range Types
251
252
```typescript { .api }
253
interface RangeValue<T> {
254
start: T;
255
end: T;
256
}
257
258
type DateRange = RangeValue<DateValue>;
259
type TimeRange = RangeValue<TimeValue>;
260
```
261
262
### Validation
263
264
Range validation includes all standard date validation plus range-specific validation.
265
266
```typescript { .api }
267
interface DateRangePickerState {
268
/** Whether the date range picker is invalid, based on validation props and range logic */
269
isInvalid: boolean;
270
/** The current validation state @deprecated Use `isInvalid` instead */
271
validationState: ValidationState | null;
272
}
273
```
274
275
Range-specific validation errors:
276
- **Range reversed**: End date is before start date
277
- **Start date invalid**: Start date violates min/max/unavailable constraints
278
- **End date invalid**: End date violates min/max/unavailable constraints
279
- **Range too long**: If custom validation restricts range duration
280
281
### Form Integration
282
283
Supports HTML forms with separate names for start and end dates.
284
285
```typescript { .api }
286
interface DateRangePickerProps<T> {
287
/** The name of the start date input element for HTML forms */
288
startName?: string;
289
/** The name of the end date input element for HTML forms */
290
endName?: string;
291
/** Whether user input is required before form submission */
292
isRequired?: boolean;
293
}
294
```
295
296
Example with form submission:
297
```typescript
298
const state = useDateRangePickerState({
299
startName: 'trip_start',
300
endName: 'trip_end',
301
isRequired: true,
302
onChange: (range) => {
303
// Form will include trip_start and trip_end fields
304
}
305
});
306
```