- Problem with finding the right date picker
- Headless-UI solution
- Talk is cheap. Show me the code
- Conclusion
Each year the βοΈ React community is becoming bigger and bigger.
And with this growth, we get more and more tools to achieve our goals and needs. Despite this incredible variety, it is hard to get what you want.
The problem
Let's dive into the problem:
- π your component library doesn't have the component that you need;
- βοΈ you need to make changes to your build process;
- π your styling system is different from the most popular solution;
- π¦Ή it's challenging to customise a component that matches your design;
- π° it could cost money to get needed components (shout to MUI π)
- ποΈ the library is heavy and doesn't have tree-shaking;
- π terrible documentation;
- βοΈ no tests, no TypeScript support, no examples...
And it is just by picking a date picker π.
The solution
The solution to all of these π problems could be a headless-UI component like @rehookify/datepicker
Main features and pros:
- small size;
- zero dependencies;
- modular design, you can use as little code as possible;
- single, multiple and range date selection;
- time selection
- several calendars support;
- support native localisation with
toLocaleDateString
andtoLocaleTimeString
; - prop-getters to give all needed props for your components;
The main drawback is that you need to style everything by yourself. But, with modern CSS
layout techniques like grid
and flex
, it is an easy and fun thing to do. It means no drawbacks at all π
By using @rehookify/datepicker you can pick a modular or all-in-one approach.
The code
Modular hooks
The example below will give you a calendar with multiple date selections π
import { useState } from 'react';
import { useDatePickerState, useCalendars } from '@rehookify/datepicker';
const DatePicker = () => {
const [selectedDates, onDatesChange] = useState<Date[]>([]);
const dpState = useDatePickerState({
selectedDates,
onDatesChange,
dates: { toggle: true, mode: 'multiple' },
});
const { calendars, weekDays } = useCalendars(dpState);
const { month, year, days } = calendars[0];
return (
<section>
<header>
<div>
<p>{month} {year}</p>
</div>
<ul>
{weekDays.map((day) => (
<li key={`${month}-${day}`}>{day}</li>
))}
</ul>
</header>
<ul>
{days.map((dpDay) => (
<li key={`${month}-${dpDay.day}`}>
<button>{dpDay.day}</button>
</li>
))}
</ul>
</section>
);
}
Modular context
In case you don't want to pass any parameters πββοΈ or want to split components, you can use Context implementation.
The example below results in the calendar with date range selections π
import { useState } from 'react';
import {
DatePickerStateProvider,
useContextCalendars,
useContextDaysPropGetters,
useContextTime,
useContextTimePropGetters,
} from '@rehookify/datepicker';
const DatePicker = () => {
const { calendars, weekDays } = useContextCalendars();
const { dayButton } = useContextDaysPropGetters();
const { year, month, days } = calendars[0];
return (
<main>
<header>
<div>
<p>{month} {year}</p>
</div>
<ul>
{weekDays.map((day) => (
<li key={`${month}-${day}`}>{day}</li>
))}
</ul>
</header>
<ul>
{days.map((dpDay) => (
<li key={`${month}-${dpDay.day}`}>
<button {...dayButton(dpDay)}>{dpDay.day}</button>
</li>
))}
</ul>
</main>
)
}
const TimePicker = () => {
const { time } = useContextTime();
const { timeButton } = useContextTimePropGetters();
return (
<ul>
{time.map((t) => (
<li key={t.$date.toString()}>
<button {...timeButton(t)}>{t.time}</>
</li>
))}
</ul>
)
}
const App = () => {
const d = new Date();
const [selectedDates, onDatesChange] = useState<Date[]>([d]);
return (
<DatePickerStateProvider
config={{
selectedDates,
focusDate: d,
onDatesChange,
dates: { mode: 'multiple' },
}}
>
<section>
<DatePicker />
<TimePicker />
</section>
</DatePickerStateProvider>
);
}
All-in-one solutions
useDatepicker
The calendar with date selection and month pagination π
import { MouseEvent, useState } from 'react';
import { useDatePicker } from '@rehookify/datepicker';
const DatePicker = () => {
const [selectedDates, onDatesChange] = useState<Date[]>([]);
const {
data: { weekDays, calendars },
propGetters: {
dayButton,
previousMonthButton,
nextMonthButton,
},
} = useDatePicker({
selectedDates,
onDatesChange,
});
// calendars[0] is always present, this is an initial calendar
const { year, month, days } = calendars[0];
const onDayClick = (evt: MouseEvent<HTMLElement>, date: Date) => {
// In case you need any action with evt
evt.stopPropagation();
// In case you need any additional action with date
console.log(date);
}
// selectedDates is an array of dates
// formatted with date.toLocaleDateString(locale, options)
return (
<section>
{selectedDates.length > 0 && <h1>{selectedDates[0]}</h1>}
<header>
<div>
<button {...previousMonthButton()}><</button>
<p>{month} {year}</p>
<button {...nextMonthButton()}>></button>
</div>
<ul>
{weekDays.map((day) => (
<li key={`${month}-${day}`}>{day}</li>
))}
</ul>
</header>
<ul>
{days.map((dpDay) => (
<li key={`${month}-${dpDay.day}`}>
<button
{...dayButton(dpDay, { onClick: onDayClick })}
>
{dpDay.day}
</button>
</li>
))}
</ul>
</section>
)
}
DatePickerProvider
import { useState } from 'react';
import {
DatePickerProvider,
useDatePickerContext,
} from '@rehookify/datepicker';
const DatePicker = () => {
const {
data: { weekDays, calendars, years, months },
} = useDatePickerContext();
const { year, month, days } = calendars[0];
return (
<section>
<header>{month} {year}</header>
...
</section>
)
}
const App = () => {
const [selectedDates, onDatesChange] = useState<Date[]>([]);
return (
<DatePickerProvider
config={{
selectedDates,
onDatesChange,
dates: { mode: 'range' },
}}
>
<DatePicker />
</DatePickerProvider>
);
}
Conclusion
@rehookify/datepicker will help you create a date picker that suits your need and tech stack. Its modular design will save as many bytes for your app as possible.
If you have any questions feel free to reach out.
Subscribe and support us on:
Image by Anton Lapko
Top comments (3)
Initial code has many issues, didn't work for me.
const { year, month, days } = calendars[0];
Property 'month' does not exist on type 'DPCalendar | undefined'.
Cannot find name 'appOffset'.
Unsafe member access .map on an
any
value.Type 'Date' is not assignable to type 'ReactNode'.
Thanks for the comment and feedback.
There are some inconsistencies in the doc, will work to update.
Please check examples
The codesandbox links provided as examples are not working. Kindly share the working links if possible, thanks for the help