useDateRangePicker для диапазонов

useDateRangePicker — это хук из библиотеки React Aria, предназначенный для создания полностью доступных интерфейсов выбора диапазона дат. Он объединяет несколько сложных компонентов пользовательского интерфейса:

  • поля ввода дат
  • кнопку открытия календаря
  • всплывающее окно с календарём
  • выбор начальной и конечной даты
  • управление фокусом и клавиатурой
  • обработку локализации и форматов дат

Хук обеспечивает доступность (ARIA), поддержку клавиатуры, локализацию, а также взаимодействует с библиотекой React Stately, которая управляет состоянием компонента.

useDateRangePicker не рендерит UI напрямую. Он предоставляет набор props, которые необходимо передать в DOM-элементы.


Архитектура Date Range Picker

Компонент выбора диапазона дат состоит из нескольких независимых частей.

Основные элементы интерфейса:

  1. Поле начала диапазона
  2. Поле конца диапазона
  3. Кнопка открытия календаря
  4. Поповер с календарём
  5. Календарь для выбора диапазона
  6. Состояние выбора

Типичная структура:

DateRangePicker
 ├─ Group
 │   ├─ DateInput (start)
 │   ├─ Separator
 │   ├─ DateInput (end)
 │   └─ Button
 └─ Popover
     └─ RangeCalendar

useDateRangePicker управляет связью между этими частями.


Установка зависимостей

Для работы необходимы несколько пакетов.

npm install @react-aria/datepicker
npm install @react-stately/datepicker
npm install @internationalized/date

Каждый пакет выполняет отдельную роль:

Пакет Назначение
React Aria доступность и взаимодействия
React Stately управление состоянием
internationalized/date работа с датами и локалями

Базовое использование

Минимальный пример использования useDateRangePicker.

import {useDateRangePicker} from "@react-aria/datepicker";
import {useDateRangePickerState} from "@react-stately/datepicker";
import {useRef} from "react";

function DateRangePicker(props) {
  let state = useDateRangePickerState(props);
  let ref = useRef();

  let {
    groupProps,
    startFieldProps,
    endFieldProps,
    buttonProps,
    dialogProps,
    calendarProps
  } = useDateRangePicker(props, state, ref);

  return (
    <div {...groupProps} ref={ref}>
      <DateField {...startFieldProps} />
      <span>—</span>
      <DateField {...endFieldProps} />
      <button {...buttonProps}>?</button>

      {state.isOpen &&
        <Popover>
          <Dialog {...dialogProps}>
            <RangeCalendar {...calendarProps} />
          </Dialog>
        </Popover>
      }
    </div>
  );
}

Связь с состоянием (useDateRangePickerState)

useDateRangePicker не управляет состоянием самостоятельно. Оно создаётся через useDateRangePickerState.

let state = useDateRangePickerState(props);

Состояние хранит:

  • выбранный диапазон
  • открыто ли всплывающее окно
  • активный сегмент
  • временное значение при вводе
  • валидацию

Основные свойства состояния:

Свойство Назначение
value текущий диапазон
setValue() обновление диапазона
isOpen открыт ли календарь
open() открыть календарь
close() закрыть календарь

Объект диапазона дат

Значение диапазона представляется объектом:

{
  start: CalendarDate,
  end: CalendarDate
}

Пример:

{
  start: new CalendarDate(2024, 4, 1),
  end: new CalendarDate(2024, 4, 10)
}

CalendarDate — тип из @internationalized/date, который не зависит от часовых поясов.


Получаемые props

useDateRangePicker возвращает набор props для различных элементов.

groupProps

Назначается контейнеру компонента.

<div {...groupProps}>

Отвечает за:

  • ARIA-структуру
  • управление фокусом
  • взаимодействие между полями

startFieldProps

Передаётся в компонент поля начала диапазона.

<DateField {...startFieldProps} />

Обеспечивает:

  • доступный ввод даты
  • навигацию по сегментам
  • локализацию формата

endFieldProps

Используется для поля окончания диапазона.

<DateField {...endFieldProps} />

React Aria автоматически:

  • связывает поля
  • контролирует корректность диапазона

buttonProps

Props для кнопки открытия календаря.

<button {...buttonProps}>?</button>

Кнопка:

  • открывает popover
  • поддерживает клавиатуру
  • имеет корректные ARIA-атрибуты

dialogProps

Используется для контейнера всплывающего окна.

<Dialog {...dialogProps}>

Отвечает за:

  • роль диалога
  • управление фокусом
  • accessibility

calendarProps

Передаётся в компонент календаря.

<RangeCalendar {...calendarProps} />

Этот компонент:

  • отображает календарь
  • позволяет выбрать диапазон
  • синхронизируется с полями ввода

Компонент RangeCalendar

Календарь для диапазонов обычно создаётся с useRangeCalendar.

Пример:

function RangeCalendar(props) {
  let state = useRangeCalendarState(props);
  let ref = useRef();

  let {calendarProps} = useRangeCalendar(props, state, ref);

  return (
    <div {...calendarProps} ref={ref}>
      {/* календарная сетка */}
    </div>
  );
}

Валидация диапазона

React Aria поддерживает несколько типов ограничений.

Минимальная дата

<DateRangePicker
  minValue={new CalendarDate(2024, 1, 1)}
/>

Максимальная дата

<DateRangePicker
  maxValue={new CalendarDate(2024, 12, 31)}
/>

Ограничение длины диапазона

Можно контролировать диапазон вручную:

function isInvalid(range) {
  if (!range) return false

  let days = differenceInDays(range.start, range.end)

  return days > 30
}

Управляемый компонент

DateRangePicker может работать в controlled режиме.

let [value, setValue] = useState(null)

<DateRangePicker
  value={value}
  onCha nge={setValue}
/>

Это позволяет:

  • синхронизировать данные с сервером
  • использовать формы
  • управлять состоянием глобально

Форматы дат и локализация

React Aria автоматически использует локаль браузера.

Для явного указания:

<I18nProvider locale="ru-RU">
  <DateRangePicker />
</I18nProvider>

Локализация влияет на:

  • порядок сегментов
  • формат даты
  • язык календаря
  • первый день недели

Работа сегментированных полей

DateField состоит из сегментов.

Пример формата:

ДД.ММ.ГГГГ

Каждый сегмент редактируется отдельно:

Сегмент Поведение
день стрелки увеличивают значение
месяц автопереход
год ввод числа

Навигация:

  • ← → перемещение между сегментами
  • ↑ ↓ изменение значения
  • ввод цифр

Управление поповером

Popover открывается через состояние:

state.open()
state.close()

Обычно используется автоматически через buttonProps.

Пример:

{state.isOpen && (
  <Popover>
    <Dialog {...dialogProps}>
      <RangeCalendar {...calendarProps} />
    </Dialog>
  </Popover>
)}

Поддержка клавиатуры

Компонент поддерживает полный набор взаимодействий.

Поля ввода

Клавиша Действие
Tab переход между полями
← → переключение сегментов
↑ ↓ изменение значения
цифры ввод даты

Календарь

Клавиша Действие
стрелки перемещение по дням
PageUp предыдущий месяц
PageDown следующий месяц
Enter выбор даты
Esc закрыть календарь

Логика выбора диапазона

Выбор происходит в два этапа.

  1. Пользователь выбирает начальную дату
  2. Затем конечную

Если конечная дата раньше начальной:

React Aria автоматически:

  • переставляет даты
  • корректирует диапазон

Disabled даты

Можно отключать отдельные дни.

<DateRangePicker
  isDateUnavailable={(date) =>
    date.day === 1
  }
/>

Такие даты:

  • не выбираются
  • отображаются как недоступные
  • игнорируются при навигации

Ограничение будущих или прошлых дат

Пример — запрет будущих дат.

<DateRangePicker
  maxValue={today(getLocalTimeZone())}
/>

Запрет прошлых:

<DateRangePicker
  minValue={today(getLocalTimeZone())}
/>

Работа с временными зонами

React Aria использует объекты:

  • CalendarDate
  • ZonedDateTime
  • CalendarDateTime

Выбор зависит от сценария.

Тип Когда использовать
CalendarDate обычные даты
CalendarDateTime дата + время
ZonedDateTime с часовым поясом

Для диапазонов чаще применяется CalendarDate.


Кастомизация интерфейса

React Aria не навязывает UI.

Можно создавать любой дизайн.

Пример:

[ 01.05.2024 ] — [ 10.05.2024 ]  ?

или

Check-in   Check-out
[ 01 May ] [ 10 May ]

Интеграция с формами

DateRangePicker легко интегрируется с формами.

Пример с HTML form:

<form>
  <DateRangePicker name="booking" />
</form>

Значение сериализуется автоматически.


Частые сценарии использования

Бронирование

  • даты заезда и выезда
  • ограничение диапазона

Фильтрация данных

  • отчёты
  • аналитика
  • логи

Планирование

  • календарные события
  • временные интервалы

Типичные ошибки

Отсутствие React Stately

Без useDateRangePickerState хук не работает.


Неправильный тип даты

Использование Date вместо CalendarDate приводит к проблемам с таймзонами.


Нарушение структуры компонентов

Важно сохранять структуру:

DateRangePicker
 ├─ DateField
 ├─ DateField
 ├─ Button
 └─ RangeCalendar

Производительность

React Aria оптимизирован для:

  • минимального количества ререндеров
  • управления фокусом
  • работы с большими календарями

Состояние хранится централизованно, поэтому даже сложные компоненты остаются производительными.


Расширение компонента

useDateRangePicker можно комбинировать с другими хуками React Aria:

  • usePopover
  • useDialog
  • useButton
  • useRangeCalendar
  • useDateField

Это позволяет строить сложные кастомные интерфейсы выбора диапазонов, полностью контролируя поведение и внешний вид.