React state management hooks for date picker components with internationalization and accessibility support.
—
State management for date field components with individually editable segments. Each part of a date value (year, month, day, hour, minute, second) is displayed in a separate editable segment, providing precise keyboard-based date and time input.
Creates a state object for managing date field component state with segment-based editing, validation, and formatting.
/**
* Provides state management for a date field component.
* A date field allows users to enter and edit date and time values using a keyboard.
* Each part of a date value is displayed in an individually editable segment.
* @param props - Configuration options including locale and calendar system
* @returns DateFieldState object with segment management and validation
*/
function useDateFieldState<T extends DateValue = DateValue>(
props: DateFieldStateOptions<T>
): DateFieldState;
interface DateFieldStateOptions<T extends DateValue = DateValue> extends DatePickerProps<T> {
/**
* The maximum unit to display in the date field.
* @default 'year'
*/
maxGranularity?: 'year' | 'month' | Granularity;
/** The locale to display and edit the value according to. */
locale: string;
/**
* A function that creates a Calendar object for a given calendar identifier.
* Such a function may be imported from the @internationalized/date package.
*/
createCalendar: (name: CalendarIdentifier) => Calendar;
}
interface DateFieldState extends FormValidationState {
/** The current field value. */
value: DateValue | null;
/** The default field value. */
defaultValue: DateValue | null;
/** The current value, converted to a native JavaScript Date object. */
dateValue: Date;
/** The calendar system currently in use. */
calendar: Calendar;
/** Sets the field's value. */
setValue(value: DateValue | null): void;
/** A list of segments for the current value. */
segments: DateSegment[];
/** A date formatter configured for the current locale and format. */
dateFormatter: DateFormatter;
/** The current validation state of the date field, based on the `validationState`, `minValue`, and `maxValue` props. @deprecated Use `isInvalid` instead. */
validationState: ValidationState | null;
/** Whether the date field is invalid, based on the `isInvalid`, `minValue`, and `maxValue` props. */
isInvalid: boolean;
/** The granularity for the field, based on the `granularity` prop and current value. */
granularity: Granularity;
/** The maximum date or time unit that is displayed in the field. */
maxGranularity: 'year' | 'month' | Granularity;
/** Whether the field is disabled. */
isDisabled: boolean;
/** Whether the field is read only. */
isReadOnly: boolean;
/** Whether the field is required. */
isRequired: boolean;
/** Increments the given segment. Upon reaching the minimum or maximum value, the value wraps around to the opposite limit. */
increment(type: SegmentType): void;
/** Decrements the given segment. Upon reaching the minimum or maximum value, the value wraps around to the opposite limit. */
decrement(type: SegmentType): void;
/** Increments the given segment by a larger amount, rounding it to the nearest increment. The amount to increment by depends on the field, for example 15 minutes, 7 days, and 5 years. Upon reaching the minimum or maximum value, the value wraps around to the opposite limit. */
incrementPage(type: SegmentType): void;
/** Decrements the given segment by a larger amount, rounding it to the nearest increment. The amount to decrement by depends on the field, for example 15 minutes, 7 days, and 5 years. Upon reaching the minimum or maximum value, the value wraps around to the opposite limit. */
decrementPage(type: SegmentType): void;
/** Sets the value of the given segment. */
setSegment(type: 'era', value: string): void;
setSegment(type: SegmentType, value: number): void;
/** Updates the remaining unfilled segments with the placeholder value. */
confirmPlaceholder(): void;
/** Clears the value of the given segment, reverting it to the placeholder. */
clearSegment(type: SegmentType): void;
/** Formats the current date value using the given options. */
formatValue(fieldOptions: FieldOptions): string;
/** Gets a formatter based on state's props. */
getDateFormatter(locale: string, formatOptions: FormatterOptions): DateFormatter;
}Usage Examples:
import { useDateFieldState } from "@react-stately/datepicker";
import { CalendarDate, createCalendar } from "@internationalized/date";
// Basic date field
function BasicDateField() {
const state = useDateFieldState({
locale: 'en-US',
createCalendar,
defaultValue: new CalendarDate(2023, 6, 15),
onChange: (value) => console.log("Date changed:", value?.toString())
});
return (
<div>
{state.segments.map((segment, index) => (
<span
key={index}
style={{
padding: '2px',
backgroundColor: segment.isPlaceholder ? '#f0f0f0' : 'white',
border: segment.isEditable ? '1px solid #ccc' : 'none'
}}
>
{segment.text}
</span>
))}
<div>Current value: {state.value?.toString()}</div>
</div>
);
}
// Time field with segment manipulation
function TimeField() {
const state = useDateFieldState({
locale: 'en-US',
createCalendar,
granularity: 'second',
maxGranularity: 'hour',
onChange: (value) => console.log("Time:", value)
});
const handleSegmentIncrement = (segment: DateSegment) => {
if (segment.isEditable) {
state.increment(segment.type);
}
};
return (
<div>
{state.segments.map((segment, index) => (
<button
key={index}
disabled={!segment.isEditable}
onClick={() => handleSegmentIncrement(segment)}
style={{
margin: '1px',
padding: '4px',
backgroundColor: segment.isPlaceholder ? '#f0f0f0' : 'white'
}}
>
{segment.text}
</button>
))}
<div>
<button onClick={() => state.confirmPlaceholder()}>
Confirm Placeholder
</button>
</div>
</div>
);
}
// Date field with validation and custom formatting
function ValidatedDateField() {
const state = useDateFieldState({
locale: 'fr-FR',
createCalendar,
minValue: new CalendarDate(2020, 1, 1),
maxValue: new CalendarDate(2030, 12, 31),
shouldForceLeadingZeros: true,
isRequired: true,
onChange: (value) => {
if (value) {
console.log("Valid date:", state.formatValue({
year: 'numeric',
month: 'long',
day: 'numeric'
}));
}
}
});
return (
<div>
<div style={{ border: state.isInvalid ? '2px solid red' : '1px solid #ccc' }}>
{state.segments.map((segment, index) => (
<input
key={index}
value={segment.text}
placeholder={segment.placeholder}
readOnly={!segment.isEditable}
onChange={(e) => {
if (segment.type === 'era') {
state.setSegment(segment.type, e.target.value);
} else {
const num = parseInt(e.target.value);
if (!isNaN(num)) {
state.setSegment(segment.type, num);
}
}
}}
style={{
width: `${segment.text.length + 2}ch`,
border: 'none',
textAlign: 'center'
}}
/>
))}
</div>
{state.isInvalid && <div style={{ color: 'red' }}>Invalid date range</div>}
</div>
);
}Each segment represents an individual editable part of the date/time value.
interface DateSegment {
/** The type of segment. */
type: SegmentType;
/** The formatted text for the segment. */
text: string;
/** The numeric value for the segment, if applicable. */
value?: number;
/** The minimum numeric value for the segment, if applicable. */
minValue?: number;
/** The maximum numeric value for the segment, if applicable. */
maxValue?: number;
/** Whether the value is a placeholder. */
isPlaceholder: boolean;
/** A placeholder string for the segment. */
placeholder: string;
/** Whether the segment is editable. */
isEditable: boolean;
}
type SegmentType = 'era' | 'year' | 'month' | 'day' | 'hour' | 'minute' | 'second' | 'dayPeriod' | 'literal' | 'timeZoneName';Methods for incrementing, decrementing, and setting individual segments.
/**
* Increments the given segment by 1. Upon reaching the maximum value, wraps to minimum.
* @param type - The segment type to increment
*/
increment(type: SegmentType): void;
/**
* Decrements the given segment by 1. Upon reaching the minimum value, wraps to maximum.
* @param type - The segment type to decrement
*/
decrement(type: SegmentType): void;
/**
* Increments the given segment by a larger amount (page step).
* Page steps: year=5, month=2, day=7, hour=2, minute=15, second=15
* @param type - The segment type to increment
*/
incrementPage(type: SegmentType): void;
/**
* Decrements the given segment by a larger amount (page step).
* @param type - The segment type to decrement
*/
decrementPage(type: SegmentType): void;
/**
* Sets the value of a specific segment
* @param type - The segment type to set
* @param value - The value to set (string for era, number for others)
*/
setSegment(type: 'era', value: string): void;
setSegment(type: SegmentType, value: number): void;
/**
* Clears the value of the given segment, reverting it to placeholder
* @param type - The segment type to clear
*/
clearSegment(type: SegmentType): void;
/**
* Updates remaining unfilled segments with the placeholder value
* Useful for completing partial date entry
*/
confirmPlaceholder(): void;The date field integrates with different calendar systems through the @internationalized/date library.
interface DateFieldStateOptions<T> {
/** The locale to display and edit the value according to. */
locale: string;
/**
* A function that creates a Calendar object for a given calendar identifier.
* Such a function may be imported from the @internationalized/date package.
*/
createCalendar: (name: CalendarIdentifier) => Calendar;
}Example calendar setup:
import { createCalendar } from "@internationalized/date";
const state = useDateFieldState({
locale: 'ar-SA', // Arabic Saudi Arabia
createCalendar, // Supports Gregory, Islamic, Buddhist, etc.
// ... other props
});Control which date/time segments are displayed and editable.
interface DateFieldStateOptions<T> {
/**
* The maximum unit to display in the date field.
* @default 'year'
*/
maxGranularity?: 'year' | 'month' | Granularity;
/** Determines the smallest unit that is displayed in the date picker. */
granularity?: Granularity;
}Examples:
granularity: 'day' - Shows year, month, daygranularity: 'minute', maxGranularity: 'day' - Shows day, hour, minutegranularity: 'second' - Shows all segments including secondsInstall with Tessl CLI
npx tessl i tessl/npm-react-stately--datepicker