Yet another datepicker in React
Yet another datepicker in React, ha? But why, if you can use react-dates
. It is a great library if you already use moment, which is a widely used library for
date and time manipulations. However, the modern web is pushing us to keep reducing bundle sizes, and that is a problem yet to be addressed by the creators of moment
.
Long story short: I decided to create my own datepicker.
Obviously bundle size was not the sole motivation. I noticed that the most used libraries haven't adopted css-in-js and react hooks or a more functional programing approach.
To sum up:
Motivation
- Support native JavaScript date, instead of the
moment
object, - RTL support,
- internationalize,
- mobile-friendly,
- selecting a specific date or range
- theming with styled-system
- written in TypeScript
- React hooks
- etc.
Check out the storybook examples or keep on reading for the technical details.
Architecture
Still with me? Let's get to business.
I wanted the datepicker to be used in a variety of scenarios, so I split the project into two libraries @datepicker-react/hooks
and @datepicker-react/styled
.
@datepicker-react/hooks
@datepicker-react/hooks
are React hooks (if you are not familiar with React hooks,
check out the official documentation), that make it easy to build a datepicker or calendar component, tailored to your needs in a few minutes.
The library contains three hooks, namely useDatepicker
, useMonth
and useDay
.
Install
yarn add @datepicker-react/hooks
useDatepicker
This hook the core of @datepicker-react/hooks
. It manages the datepicker logic. You can control the selected dates using the startDate
, endDate
, and onDatesChange
props as shown below.
const {activeMonths,isDateSelected,isFirstOrLastSelectedDate,isDateHovered,firstDayOfWeek,onDateSelect,onResetDates,goToPreviousMonths,goToNextMonths,numberOfMonths,hoveredDate,onDateHover,isDateFocused,focusedDate,onDateFocus,isDateBlocked,} = useDatepicker({startDate: new Date(2019, 1, 1),endDate: new Date(2019, 1, 10),focusedInput: null,onDatesChange: (props) => {console.log(props)},})
useMonth
useMonth
returns an array of days
, weekDayLabels
and monthLabel
, depends on the year
and month
props.
const {days,weekdayLabels,monthLabel,} = useMonth({year: 2019,month: 1,})
useDay
Returns callbacks and flags used for styling.
const {tabIndex,isSelected,isSelectedStartOrEnd,isWithinHoverRange,disabledDate,onKeyDown,onClick,onMouseEnter,} = useMonth({date,focusedDate,isDateSelected,isDateFocused,isFirstOrLastSelectedDate,isDateHovered,isDateBlocked,onDateSelect,onDateFocus,onDateHover,dayRef,})
@datepicker-react/styled
This part of the library is used for styling. As mentioned above it leverages css-in-js
. There are quite a few great libraries to choose from,
but I decided to pick styled-components, since it's the one I am most comfortable with.
The result is an easily internationalizable, accessible, mobile-friendly datepicker library for the web. It supports RTL and theming with styled-system.
The library can be used as a single date input (DateSingleInput
), a input range (DateRangeInput
) or as a "classical" datepicker component (Datepicker
)
Install
yarn add @datepicker-react/styled styled-components
Include components
import {DateRangeInput, DateSingleInput, Datepicker} from '@datepicker-react/styled'
DateRangeInput example
The DateRangeInput
is a fully controlled component that allows users to select a date range. You
can control the selected dates using the startDate
, endDate
, and onDatesChange
props as shown
below. Similarly, you can control which input is focused as well as calendar visibility (the
calendar is only visible if focusedInput
is defined) with the focusedInput
and onFocusChange
props as shown below.
Here is the minimum REQUIRED setup you need to get the DateRangeInput
working:
import React, {useReducer} from 'react'import {DateRangeInput} from '@datepicker-react/styled'const initialState = {startDate: null,endDate: null,focusedInput: null,}function reducer(state, action) {switch (action.type) {case 'focusChange':return {...state, focusedInput: action.payload}case 'dateChange':return action.payloaddefault:throw new Error()}}function App() {const [state, dispatch] = useReducer(reducer, initialState)return (<DateRangeInputonDatesChange={data => dispatch({type: 'dateChange', payload: data})}onFocusChange={focusedInput => dispatch({type: 'focusChange', payload: focusedInput})}startDate={state.startDate} // Date or nullendDate={state.endDate} // Date or nullfocusedInput={state.focusedInput} // START_DATE, END_DATE or null/>)}