or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

date-field-state.mddate-picker-state.mddate-range-picker-state.mdindex.mdtime-field-state.md

date-range-picker-state.mddocs/

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

```