Lesson 4: Selection Modes
Implementing single, range, and multi-select with useCalendar.
Lesson 4 of 8: wire up useCalendar and see how single, range, and multi-select work.
The useCalendar hook
useCalendar wraps the grid builder and selection logic in a stateful React hook. It returns state, actions, and prop getters.
import { NativeAdapter, useCalendar } from '@gentleduck/calendar'
const adapter = new NativeAdapter()
function MyCalendar() {
const { state, getDayProps, getGridProps, getHeaderProps, getNavProps } = useCalendar({
adapter,
mode: 'single',
})
// ...render
}import { NativeAdapter, useCalendar } from '@gentleduck/calendar'
const adapter = new NativeAdapter()
function MyCalendar() {
const { state, getDayProps, getGridProps, getHeaderProps, getNavProps } = useCalendar({
adapter,
mode: 'single',
})
// ...render
}Single selection
The simplest mode. Click a day to select it, click again to deselect.
const { state } = useCalendar({ adapter, mode: 'single' })
// state.value: Date | nullconst { state } = useCalendar({ adapter, mode: 'single' })
// state.value: Date | nullfunction SingleSelect() {
const { state, getDayProps, getGridProps } = useCalendar({
adapter,
mode: 'single',
onSelect: (date) => console.log('Selected:', date),
})
return (
<div {...getGridProps()}>
{state.weeks.map(week =>
week.days.map(day => (
<button key={day.date.getTime()} {...getDayProps(day)}>
{day.date.getDate()}
</button>
))
)}
</div>
)
}function SingleSelect() {
const { state, getDayProps, getGridProps } = useCalendar({
adapter,
mode: 'single',
onSelect: (date) => console.log('Selected:', date),
})
return (
<div {...getGridProps()}>
{state.weeks.map(week =>
week.days.map(day => (
<button key={day.date.getTime()} {...getDayProps(day)}>
{day.date.getDate()}
</button>
))
)}
</div>
)
}Range selection
Click the start date, then the end date. The range fills in between.
const { state } = useCalendar({ adapter, mode: 'range' })
// state.value: { from: Date, to: Date | null } | nullconst { state } = useCalendar({ adapter, mode: 'range' })
// state.value: { from: Date, to: Date | null } | nullWhen only from is set (one click in), hovering over days shows a preview range. Style the preview through the data-range-middle attribute.
Multi selection
Click to toggle dates on or off.
const { state } = useCalendar({ adapter, mode: 'multi' })
// state.value: Date[]const { state } = useCalendar({ adapter, mode: 'multi' })
// state.value: Date[]Controlled vs uncontrolled
Standard React patterns. Control selection externally if needed:
// Uncontrolled (manages its own state)
useCalendar({
adapter,
mode: 'single',
defaultSelected: new Date(2026, 2, 15),
onSelect: (value) => console.log(value),
})
// Controlled (you manage the state)
const [selected, setSelected] = React.useState<Date | null>(null)
useCalendar({
adapter,
mode: 'single',
selected,
onSelect: setSelected,
})// Uncontrolled (manages its own state)
useCalendar({
adapter,
mode: 'single',
defaultSelected: new Date(2026, 2, 15),
onSelect: (value) => console.log(value),
})
// Controlled (you manage the state)
const [selected, setSelected] = React.useState<Date | null>(null)
useCalendar({
adapter,
mode: 'single',
selected,
onSelect: setSelected,
})Constraining selection
Disable specific dates or set boundaries:
useCalendar({
adapter,
mode: 'single',
// Disable weekends
disabled: (date) => date.getDay() === 0 || date.getDay() === 6,
// Only allow dates in March 2026
fromDate: new Date(2026, 2, 1),
toDate: new Date(2026, 2, 31),
})useCalendar({
adapter,
mode: 'single',
// Disable weekends
disabled: (date) => date.getDay() === 0 || date.getDay() === 6,
// Only allow dates in March 2026
fromDate: new Date(2026, 2, 1),
toDate: new Date(2026, 2, 31),
})