Contexts

Gói headless cung cấp các Context provider bao bọc các hook và hiển thị trạng thái của chúng thông qua React Context. Điều này cho phép sử dụng mẫu thành phần phức hợp (compound component pattern) — chia nhỏ giao diện người dùng của bạn thành các thành phần con nhỏ, tập trung đọc trạng thái từ context thay vì truyền props xuống.

Tại sao dùng Contexts?

Nếu không có context, bạn sẽ phải luồn mọi phần của trạng thái qua props:

// Không có context -- prop drilling
function 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} ... />
);
}

Với context, các thành phần con sử dụng những gì chúng cần một cách trực tiếp:

// Với context -- các thành phần phức hợp
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>;
}

Các Provider có sẵn

ProviderHook được dùng nội bộHook người dùng
DatePickerProvideruseDatePickeruseDatePickerContext()
DateRangePickerProvideruseDateRangePickeruseDateRangePickerContext()
DateTimePickerProvideruseDateTimePickeruseDateTimePickerContext()
DateRangeTimePickerProvideruseDateRangeTimePickeruseDateRangeTimePickerContext()

Mỗi provider hiển thị cả một context đã được định kiểu (dành riêng cho loại picker đó) và PickerContext thống nhất (được chia sẻ trên tất cả các loại).

import {
DatePickerProvider,
DateRangePickerProvider,
DateTimePickerProvider,
DateRangeTimePickerProvider,
} from "react-date-range-picker-headless";

DatePickerProvider

Bao bọc useDatePicker. Cung cấp DatePickerContext + PickerContext.

import type { DatePickerProviderProps } from "react-date-range-picker-headless";
PropKiểuMô tả
childrenReactNodeBắt buộc. Các thành phần con.
valueDate | nullBắt buộc. Ngày hiện tại.
onChange(date: Date | null) => voidBắt buộc. Hàm xử lý thay đổi.
inlinebooleanHiển thị nội tuyến (không có popup).
sizeDatePickerSizeBiến thể kích thước.
placeholderstringPlaceholder cho trigger.
namestringTên trường của form.
…restBaseDatePickerOptionsTất cả các tùy chọn cơ bản (minDate, maxDate, locale, v.v.).

Hook người dùng: useDatePickerContext() trả về UseDatePickerReturn.

DateRangePickerProvider

Bao bọc useDateRangePicker. Cung cấp DateRangePickerContext + PickerContext.

import type { DateRangePickerProviderProps } from "react-date-range-picker-headless";
PropKiểuMô tả
childrenReactNodeBắt buộc. Các thành phần con.
value{ start: Date | null; end: Date | null }Bắt buộc. Khoảng ngày hiện tại.
onChange(value: { start: Date | null; end: Date | null }) => voidBắt buộc. Hàm xử lý thay đổi.
inlinebooleanHiển thị nội tuyến.
sizeDatePickerSizeBiến thể kích thước.
placeholderstringPlaceholder cho trigger.
namestringTên trường của form.
maxDaysnumberSố ngày tối đa trong khoảng.
minDaysnumberSố ngày tối thiểu trong khoảng.
presetsDateRangePreset[]Các thiết lập sẵn cho khoảng ngày.
allowSingleDateInRangebooleanCho phép khoảng một ngày.
…restBaseDatePickerOptionsTất cả các tùy chọn cơ bản.

Hook người dùng: useDateRangePickerContext() trả về UseDateRangePickerReturn.

DateTimePickerProvider

Bao bọc useDateTimePicker. Cung cấp DateTimePickerContext + PickerContext.

import type { DateTimePickerProviderProps } from "react-date-range-picker-headless";
PropKiểuMô tả
childrenReactNodeBắt buộc. Các thành phần con.
valueDate | nullBắt buộc. Ngày-giờ hiện tại.
onChange(dateTime: Date | null) => voidBắt buộc. Hàm xử lý thay đổi.
timeTimeConfigCấu hình thời gian.
inlinebooleanHiển thị nội tuyến.
sizeDatePickerSizeBiến thể kích thước.
placeholderstringPlaceholder cho trigger.
namestringTên trường của form.
…restBaseDatePickerOptionsTất cả các tùy chọn cơ bản.

Hook người dùng: useDateTimePickerContext() trả về UseDateTimePickerReturn.

DateRangeTimePickerProvider

Bao bọc useDateRangeTimePicker. Cung cấp DateRangeTimePickerContext + PickerContext.

import type { DateRangeTimePickerProviderProps } from "react-date-range-picker-headless";
PropKiểuMô tả
childrenReactNodeBắt buộc. Các thành phần con.
value{ start: Date | null; end: Date | null }Bắt buộc. Khoảng ngày hiện tại.
onChange(value: { start: Date | null; end: Date | null }) => voidBắt buộc. Hàm xử lý thay đổi.
timeTimeConfigCấu hình thời gian.
inlinebooleanHiển thị nội tuyến.
sizeDatePickerSizeBiến thể kích thước.
placeholderstringPlaceholder cho trigger.
namestringTên trường của form.
maxDaysnumberSố ngày tối đa trong khoảng.
minDaysnumberSố ngày tối thiểu trong khoảng.
presetsDateRangePreset[]Các thiết lập sẵn cho khoảng ngày.
allowSingleDateInRangebooleanCho phép khoảng một ngày.
…restBaseDatePickerOptionsTất cả các tùy chọn cơ bản.

Hook người dùng: useDateRangeTimePickerContext() trả về UseDateRangeTimePickerReturn.

PickerContext (Thống nhất)

Mỗi provider cũng đưa vào một PickerContext thống nhất. Đây là một context cha chứa tất cả các trường từ mọi loại picker, với các trường tùy chọn cho các giá trị dành riêng cho picker.

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 thống nhất cho phép bạn xây dựng các thành phần con dùng chung hoạt động trên tất cả các loại picker. Các gói đã được tạo kiểu (styled, tailwind3, tailwind4) sử dụng mẫu này trong nội bộ — các thành phần Header, Grid, Footer, v.v. của chúng đều sử dụng PickerContext.

Các trường của PickerContext

import { PickerContext, usePickerContext } from "react-date-range-picker-headless";
import type { PickerContextValue } from "react-date-range-picker-headless";
function usePickerContext(): PickerContextValue;

Sẽ báo lỗi nếu được sử dụng bên ngoài một provider của picker.

Các trường cốt lõi (luôn có mặt)

TrườngKiểu
isOpenboolean
localeLocale
focusedDateDate | null
displayValuestring
hasValueboolean
canConfirmboolean
calendarsCalendarMonth[]
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
containerRefRefObject<HTMLDivElement | null>
popupRefRefObject<HTMLDivElement | null>
yearsnumber[]
monthsnumber[]
handleYearSelect(year: number, calendarIndex?: number) => void
handleMonthSelect(month: number, calendarIndex?: number) => void

Các trường chuyển qua cho UI

TrườngKiểu
valueunknown
endNamestring | undefined
requiredboolean | undefined
inlineboolean | undefined
showOutsideDaysboolean | undefined
sizeDatePickerSize | undefined
placeholderstring | undefined
namestring | undefined
captionLayoutCaptionLayout | undefined

Các trường dành riêng cho khoảng ngày (tùy chọn)

TrườngKiểu
handleDateHover((date: Date | null) => void) | undefined
presetsDateRangePreset[] | undefined
handlePresetClick((preset: DateRangePreset) => void) | undefined
activePresetIndexnumber | undefined

Các trường thời gian đơn (tùy chọn, DateTimePicker)

TrườngKiểu
resolvedTimeConfigRequired<TimeConfig> | undefined
tempHournumber | undefined
tempMinutenumber | undefined
tempSecondnumber | undefined
tempPeriodTimePeriod | undefined
handleHourChange((hour: number) => void) | undefined
handleMinuteChange((minute: number) => void) | undefined
handleSecondChange((second: number) => void) | undefined
handlePeriodChange((period: TimePeriod) => void) | undefined
isTimePickerOpenboolean | undefined
handleTimePickerOpen(() => void) | undefined
handleTimePickerClose(() => void) | undefined
handleTimePickerConfirm(() => void) | undefined
handleTimePickerCancel(() => void) | undefined
timePickerRefRefObject<HTMLDivElement | null> | undefined
timeDisplayValuestring | undefined

Các trường thời gian cho khoảng ngày (tùy chọn, DateRangeTimePicker)

TrườngKiểu
startHournumber | undefined
startMinutenumber | undefined
startSecondnumber | undefined
startPeriodTimePeriod | undefined
endHournumber | undefined
endMinutenumber | undefined
endSecondnumber | undefined
endPeriodTimePeriod | 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
isStartTimePickerOpenboolean | undefined
isEndTimePickerOpenboolean | 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
startTimePickerRefRefObject<HTMLDivElement | null> | undefined
endTimePickerRefRefObject<HTMLDivElement | null> | undefined
startTimeDisplayValuestring | undefined
endTimeDisplayValuestring | undefined

Context đã được định kiểu vs Context thống nhất

Trường hợp sử dụngContext nào
Xây dựng một thành phần dùng chung hoạt động trên tất cả các loại pickerusePickerContext()
Xây dựng một thành phần dành riêng cho một picker với an toàn kiểu đầy đủuseDatePickerContext(), useDateRangePickerContext(), v.v.

Các context đã được định kiểu trả về kiểu trả về chính xác của hook, vì vậy bạn nhận được suy luận kiểu TypeScript đầy đủ. Context thống nhất sử dụng các trường tùy chọn cho các giá trị dành riêng cho picker.

Mẫu Context kép

Mỗi provider hiển thị hai context lồng nhau:

<SpecificPickerContext.Provider value={hookReturn}>
<PickerContext.Provider value={unifiedValue}>{children}</PickerContext.Provider>
</SpecificPickerContext.Provider>
  • Sử dụng usePickerContext() cho các thành phần con dùng chung (hoạt động với mọi picker)
  • Sử dụng hook người dùng đã được định kiểu cho logic dành riêng cho picker (an toàn kiểu đầy đủ)

Ví dụ: Date Picker phức hợp

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>
);
}
Context-driven calendar (always open via initialOpen):
March 2026
Su
Mo
Tu
We
Th
Fr
Sa