Context
headless パッケージは、フックをラップし、その状態を React Context 経由で公開する Context プロバイダーを提供します。これにより、コンパウンドコンポーネントパターンが可能になります。UI を小さく、焦点を絞ったサブコンポーネントに分割し、props を下に渡す代わりに context から状態を読み取ります。
なぜ Context を使うのか?
Context がなければ、すべての状態を props を通して渡すことになります:
// Contextなし -- prop drillingfunction MyDatePicker() { const picker = useDatePicker({ value, onChange }); return ( <MyTrigger displayValue={picker.displayValue} onToggle={picker.handleToggle} /> <MyCalendar calendar={picker.calendar} getDayProps={picker.getDayProps} ... /> <MyFooter onConfirm={picker.handleConfirm} onCancel={picker.handleCancel} ... /> );}Context を使えば、サブコンポーネントは必要なものを直接消費します:
// Contextあり -- コンパウンドコンポーネントfunction MyDatePicker() { return ( <DatePickerProvider value={value} onChange={onChange}> <ExampleWrapper variant="headless" client:visible> <MyTrigger client:visible /> </ExampleWrapper> <ExampleWrapper variant="headless" client:visible> <MyCalendar client:visible /> </ExampleWrapper> <ExampleWrapper variant="headless" client:visible> <MyFooter client:visible /> </ExampleWrapper> </DatePickerProvider> );}
function MyTrigger() { const { displayValue, handleToggle } = useDatePickerContext(); return <button onClick={handleToggle}>{displayValue}</button>;}利用可能なプロバイダー
| プロバイダー | 内部で使用されるフック | コンシューマーフック |
|---|---|---|
DatePickerProvider | useDatePicker | useDatePickerContext() |
DateRangePickerProvider | useDateRangePicker | useDateRangePickerContext() |
DateTimePickerProvider | useDateTimePicker | useDateTimePickerContext() |
DateRangeTimePickerProvider | useDateRangeTimePicker | useDateRangeTimePickerContext() |
各プロバイダーは、型付けされた context(そのピッカータイプに固有)と統一された PickerContext(すべてのタイプで共有)の両方をレンダリングします。
import { DatePickerProvider, DateRangePickerProvider, DateTimePickerProvider, DateRangeTimePickerProvider,} from "react-date-range-picker-headless";DatePickerProvider
useDatePicker をラップします。DatePickerContext + PickerContext を提供します。
import type { DatePickerProviderProps } from "react-date-range-picker-headless";| Prop | 型 | 説明 |
|---|---|---|
children | ReactNode | 必須。 子コンポーネント。 |
value | Date | null | 必須。 現在の日付。 |
onChange | (date: Date | null) => void | 必須。 変更ハンドラー。 |
inline | boolean | インラインでレンダリング(ポップアップなし)。 |
size | DatePickerSize | サイズのバリアント。 |
placeholder | string | トリガーのプレースホルダー。 |
name | string | フォームフィールド名。 |
| …rest | BaseDatePickerOptions | すべての基本オプション(minDate, maxDate, locale など)。 |
コンシューマーフック: useDatePickerContext() は UseDatePickerReturn を返します。
DateRangePickerProvider
useDateRangePicker をラップします。DateRangePickerContext + PickerContext を提供します。
import type { DateRangePickerProviderProps } from "react-date-range-picker-headless";| Prop | 型 | 説明 |
|---|---|---|
children | ReactNode | 必須。 子コンポーネント。 |
value | { start: Date | null; end: Date | null } | 必須。 現在の範囲。 |
onChange | (value: { start: Date | null; end: Date | null }) => void | 必須。 変更ハンドラー。 |
inline | boolean | インラインでレンダリング。 |
size | DatePickerSize | サイズのバリアント。 |
placeholder | string | トリガーのプレースホルダー。 |
name | string | フォームフィールド名。 |
maxDays | number | 範囲内の最大日数。 |
minDays | number | 範囲内の最小日数。 |
presets | DateRangePreset[] | 範囲のプリセット。 |
allowSingleDateInRange | boolean | 単一日での範囲を許可。 |
| …rest | BaseDatePickerOptions | すべての基本オプション。 |
コンシューマーフック: useDateRangePickerContext() は UseDateRangePickerReturn を返します。
DateTimePickerProvider
useDateTimePicker をラップします。DateTimePickerContext + PickerContext を提供します。
import type { DateTimePickerProviderProps } from "react-date-range-picker-headless";| Prop | 型 | 説明 |
|---|---|---|
children | ReactNode | 必須。 子コンポーネント。 |
value | Date | null | 必須。 現在の日時。 |
onChange | (dateTime: Date | null) => void | 必須。 変更ハンドラー。 |
time | TimeConfig | 時刻の設定。 |
inline | boolean | インラインでレンダリング。 |
size | DatePickerSize | サイズのバリアント。 |
placeholder | string | トリガーのプレースホルダー。 |
name | string | フォームフィールド名。 |
| …rest | BaseDatePickerOptions | すべての基本オプション。 |
コンシューマーフック: useDateTimePickerContext() は UseDateTimePickerReturn を返します。
DateRangeTimePickerProvider
useDateRangeTimePicker をラップします。DateRangeTimePickerContext + PickerContext を提供します。
import type { DateRangeTimePickerProviderProps } from "react-date-range-picker-headless";| Prop | 型 | 説明 |
|---|---|---|
children | ReactNode | 必須。 子コンポーネント。 |
value | { start: Date | null; end: Date | null } | 必須。 現在の範囲。 |
onChange | (value: { start: Date | null; end: Date | null }) => void | 必須。 変更ハンドラー。 |
time | TimeConfig | 時刻の設定。 |
inline | boolean | インラインでレンダリング。 |
size | DatePickerSize | サイズのバリアント。 |
placeholder | string | トリガーのプレースホルダー。 |
name | string | フォームフィールド名。 |
maxDays | number | 範囲内の最大日数。 |
minDays | number | 範囲内の最小日数。 |
presets | DateRangePreset[] | 範囲のプリセット。 |
allowSingleDateInRange | boolean | 単一日での範囲を許可。 |
| …rest | BaseDatePickerOptions | すべての基本オプション。 |
コンシューマーフック: useDateRangeTimePickerContext() は UseDateRangeTimePickerReturn を返します。
PickerContext (統一)
すべてのプロバイダーは、統一された PickerContext も注入します。これは、すべてのピッカータイプのすべてのフィールドを含むスーパーセットの context であり、ピッカー固有の値のためのオプショナルなフィールドがあります。
import { usePickerContext } from "react-date-range-picker-headless";
function MyGenericHeader() { const { locale, handlePrevMonth, handleNextMonth, calendars } = usePickerContext(); return ( <div> <button onClick={handlePrevMonth}>{locale.prevMonth}</button> <span>{locale.formatMonthYear(calendars[0].month)}</span> <button onClick={handleNextMonth}>{locale.nextMonth}</button> </div> );}統一された context を使用すると、すべてのピッカータイプで機能する共有サブコンポーネントを構築できます。スタイル付きパッケージ(styled、tailwind3、tailwind4)は、このパターンを内部で使用しています。— それらの Header、Grid、Footer などのコンポーネントはすべて PickerContext を消費します。
PickerContext のフィールド
import { PickerContext, usePickerContext } from "react-date-range-picker-headless";import type { PickerContextValue } from "react-date-range-picker-headless";function usePickerContext(): PickerContextValue;ピッカープロバイダーの外部で使用するとエラーをスローします。
コアフィールド(常に存在)
| Field | Type |
|---|---|
isOpen | boolean |
locale | Locale |
focusedDate | Date | null |
displayValue | string |
hasValue | boolean |
canConfirm | boolean |
calendars | CalendarMonth[] |
getDayProps | (date: Date, referenceMonth?: Date) => DayProps |
handleToggle | () => void |
handleOpen | () => void |
handleClose | () => void |
handleConfirm | () => void |
handleCancel | () => void |
handleClear | () => void |
handleGoToToday | () => void |
handlePrevMonth | () => void |
handleNextMonth | () => void |
handleKeyDown | (e: KeyboardEvent<HTMLElement>) => void |
handleDateClick | (date: Date) => void |
containerRef | RefObject<HTMLDivElement | null> |
popupRef | RefObject<HTMLDivElement | null> |
years | number[] |
months | number[] |
handleYearSelect | (year: number, calendarIndex?: number) => void |
handleMonthSelect | (month: number, calendarIndex?: number) => void |
UI パススルーフィールド
| Field | Type |
|---|---|
value | unknown |
endName | string | undefined |
required | boolean | undefined |
inline | boolean | undefined |
showOutsideDays | boolean | undefined |
size | DatePickerSize | undefined |
placeholder | string | undefined |
name | string | undefined |
captionLayout | CaptionLayout | undefined |
範囲指定フィールド(オプショナル)
| Field | Type |
|---|---|
handleDateHover | ((date: Date | null) => void) | undefined |
presets | DateRangePreset[] | undefined |
handlePresetClick | ((preset: DateRangePreset) => void) | undefined |
activePresetIndex | number | undefined |
単一時刻フィールド(オプショナル, DateTimePicker)
| Field | Type |
|---|---|
resolvedTimeConfig | Required<TimeConfig> | undefined |
tempHour | number | undefined |
tempMinute | number | undefined |
tempSecond | number | undefined |
tempPeriod | TimePeriod | undefined |
handleHourChange | ((hour: number) => void) | undefined |
handleMinuteChange | ((minute: number) => void) | undefined |
handleSecondChange | ((second: number) => void) | undefined |
handlePeriodChange | ((period: TimePeriod) => void) | undefined |
isTimePickerOpen | boolean | undefined |
handleTimePickerOpen | (() => void) | undefined |
handleTimePickerClose | (() => void) | undefined |
handleTimePickerConfirm | (() => void) | undefined |
handleTimePickerCancel | (() => void) | undefined |
timePickerRef | RefObject<HTMLDivElement | null> | undefined |
timeDisplayValue | string | undefined |
範囲時刻フィールド(オプショナル, DateRangeTimePicker)
| Field | Type |
|---|---|
startHour | number | undefined |
startMinute | number | undefined |
startSecond | number | undefined |
startPeriod | TimePeriod | undefined |
endHour | number | undefined |
endMinute | number | undefined |
endSecond | number | undefined |
endPeriod | TimePeriod | undefined |
handleStartHourChange | ((hour: number) => void) | undefined |
handleStartMinuteChange | ((minute: number) => void) | undefined |
handleStartSecondChange | ((second: number) => void) | undefined |
handleStartPeriodChange | ((period: TimePeriod) => void) | undefined |
handleEndHourChange | ((hour: number) => void) | undefined |
handleEndMinuteChange | ((minute: number) => void) | undefined |
handleEndSecondChange | ((second: number) => void) | undefined |
handleEndPeriodChange | ((period: TimePeriod) => void) | undefined |
isStartTimePickerOpen | boolean | undefined |
isEndTimePickerOpen | boolean | undefined |
handleStartTimePickerOpen | (() => void) | undefined |
handleStartTimePickerClose | (() => void) | undefined |
handleStartTimePickerConfirm | (() => void) | undefined |
handleStartTimePickerCancel | (() => void) | undefined |
handleEndTimePickerOpen | (() => void) | undefined |
handleEndTimePickerClose | (() => void) | undefined |
handleEndTimePickerConfirm | (() => void) | undefined |
handleEndTimePickerCancel | (() => void) | undefined |
startTimePickerRef | RefObject<HTMLDivElement | null> | undefined |
endTimePickerRef | RefObject<HTMLDivElement | null> | undefined |
startTimeDisplayValue | string | undefined |
endTimeDisplayValue | string | undefined |
型付き vs 統一 Context
| ユースケース | どの Context |
|---|---|
| すべてのピッカータイプで動作する共有コンポーネントを構築する | usePickerContext() |
| 完全な型安全性を備えた、1つのピッカーに特化したコンポーネントを構築する | useDatePickerContext(), useDateRangePickerContext(), etc. |
型付けされた context は正確なフックの戻り値の型を返すため、完全な TypeScript の型推論が得られます。統一された context は、ピッカー固有の値に対してオプショナルなフィールドを使用します。
デュアル Context パターン
すべてのプロバイダーは、2つのネストされた context をレンダリングします:
<SpecificPickerContext.Provider value={hookReturn}> <PickerContext.Provider value={unifiedValue}>{children}</PickerContext.Provider></SpecificPickerContext.Provider>- 共有サブコンポーネントには
usePickerContext()を使用します(どのピッカーでも動作します) - ピッカー固有のロジックには、型付けされたコンシューマーフックを使用します(完全な型安全性)
例:コンパウンドデートピッカー
import { useState } from "react";import { DatePickerProvider, usePickerContext, type DayProps,} from "react-date-range-picker-headless";
/** A child component that reads from PickerContext instead of receiving props. */function CalendarDisplay() { const ctx = usePickerContext();
const calendar = ctx.calendars[0];
return ( <div style={{ border: "1px solid #ccc", padding: 16, borderRadius: 8, background: "#fff" }}> {/* Header */} <div style={{ display: "flex", justifyContent: "space-between", marginBottom: 8 }}> <button onClick={ctx.handlePrevMonth}>←</button> <span>{ctx.locale.formatMonthYear(calendar.month)}</span> <button onClick={ctx.handleNextMonth}>→</button> </div>
{/* Weekdays */} <div style={{ display: "grid", gridTemplateColumns: "repeat(7, 1fr)", textAlign: "center" }}> {ctx.locale.weekdays.map((wd) => ( <div key={wd} style={{ fontSize: 12, color: "#888", padding: 4 }}> {wd} </div> ))} </div>
{/* Days */} <div style={{ display: "grid", gridTemplateColumns: "repeat(7, 1fr)", textAlign: "center" }}> {calendar.days.map((date, i) => { if (!date) return <div key={i} />; const dp: DayProps = ctx.getDayProps(date); return ( <button key={i} onClick={() => ctx.handleDateClick(date)} disabled={dp.isDisabled} style={{ padding: 8, cursor: dp.isDisabled ? "not-allowed" : "pointer", background: dp.isSelected ? "#3b82f6" : "transparent", color: dp.isSelected ? "#fff" : dp.isToday ? "#3b82f6" : dp.isDisabled ? "#ccc" : "#333", fontWeight: dp.isToday ? "bold" : "normal", border: "none", borderRadius: 4, }} > {dp.day} </button> ); })} </div>
{/* Footer */} <div style={{ display: "flex", justifyContent: "flex-end", gap: 8, marginTop: 8 }}> <button onClick={ctx.handleCancel}>Cancel</button> <button onClick={ctx.handleConfirm} disabled={!ctx.canConfirm}> Confirm </button> </div> </div> );}
/** Main example wrapping with DatePickerProvider so children can use usePickerContext. */function ContextExample() { const [value, setValue] = useState<Date | null>(null);
return ( <div style={{ fontFamily: "system-ui" }}> <DatePickerProvider value={value} onChange={setValue} initialOpen> <div style={{ marginBottom: 8, fontSize: 14, color: "#555" }}> Context-driven calendar (always open via initialOpen): </div> <CalendarDisplay /> </DatePickerProvider> </div> );}