Creating a custom React datepicker - LogRocket Blog (2024)

Editor’s note: This post was last updated by Carlos Mucuho on 21 March 2024 to add advanced features to our custom React datepicker, including a time picker, the ability to block certain dates from the calendar, and more.

Creating a custom React datepicker - LogRocket Blog (1)

It’s quite common to see forms on the web that include one or more date fields. Whether it’s a person’s date of birth or a flight schedule, you want to ensure that the user supplies a valid date. A datepicker is a helpful addition to a form that displays a calendar along with a date input field, making it easy for users to schedule events or fill out registration forms.

HTML5 introduced a date input type to improve date values in forms. The date input type presents a datepicker to the user, but the appearance of this datepicker is inconsistent across browsers:

Creating a custom React datepicker - LogRocket Blog (2)

Most major browsers devalue the date input type to a text input for the user, causing issues both for the rendering of the UI and the formatting of data. When a user enters a text-based input, formatting issues can occur with the date. For example, with various date formats used globally, a lack of instructions for the user can cause difficulty when processing the data, resulting in a browser error message.

In this article, we’ll solve the issue by building a custom React datepicker from scratch using native JavaScript Date objects. Here’s what our custom datepicker will look like:

Creating a custom React datepicker - LogRocket Blog (3)

To follow along with this tutorial, you’ll need the following:

  • Familiarity with JavaScript and React
  • Node.js installed on your machine
  • Yarn package manager installed on your machine
  • Create React App installed globally on your machine

Note: If youre using npm ≥ v5.2, then you don’t need to install Create React App as a global dependency . Instead, you can use the npx command.

If you get stuck during this tutorial, please reference the GitHub repository. Let’s get started!

Creating a new React application

To create a new React application, run the command below, which acts as a quick start for you to create the custom React datepicker:

npx create-react-app react-custom-datepickercd react-custom-datepicker

Alternatively, if you’re using Yarn, you can run the code below:

yarn create react-app react-custom-datepicker

npx is available in npm v≥5.2, which you can use to install Create React App globally on your machine. If you’ve installed CRA using the command below, you should uninstall it to ensure that npx is using the latest version:

npm install -g create-react-app

To uninstall the package, you can use either of the following commands:

//npmnpm uninstall -g create-react-app //yarnyarn global remove create-react-app

To install the required dependencies via Yarn, run the following command:

yarn add [emailprotected] reactstrap styled-components prop-types

And to install dependencies via npm, run the following command:

npm install [emailprotected] reactstrap styled-components

To provide default styling, we’ve installed the Bootstrap package as a dependency. To include the Bootstrap v5 styles, edit the src/index.js file and add the following line before every other import statement:

import "bootstrap/dist/css/bootstrap.min.css";

Setting up the directory components

Our application requires the following components:

  1. calendar: Renders the custom calendar, providing functionality for date selection
  2. datepicker: Renders a date input and presents the calendar for the user to select the date

We’ll store each component in its own directory with two files, index.js and styles.js. The index.js file exports the component, while the styles.js file exports styled-components, which is required by the component to add styling.

To create the component directories and files, run the following commands from your project’s root directory:

# Create directoriesmkdir -p src/components/Calendar src/components/Datepicker# Create filestouch src/components/Calendar/index.js src/components/Calendar/styles.jstouch src/components/Datepicker/index.js src/components/Datepicker/styles.js

Our application doesn’t require any external dependencies to handle dates. Instead, we’ll use helper functions. To create a file for the calendar helpers module, run the following commands:

mkdir -p src/helperstouch src/helpers/calendar.js

Start the application by running either of the following commands in your terminal:

//yarnyarn start//npmnpm start

Now, we can begin development. You’ll notice that a new browser tab has opened for you with live reloading functionality, keeping in sync with changes in the application.

Calendar helpers module

In the calendar helpers module we created earlier, we’ll define some helpers and constants, which we’ll export as named exports. Add the following code to the src/helpers/calendar.js file:

// (int) The current yearexport const THIS_YEAR = +(new Date().getFullYear());// (int) The current month starting from 1 - 12// 1 => January, 12 => Decemberexport const THIS_MONTH = +(new Date().getMonth()) + 1;// Week days names and shortnamesexport const WEEK_DAYS = { Sunday: "Sun", Monday: "Mon", Tuesday: "Tue", Wednesday: "Wed", Thursday: "Thu", Friday: "Fri", Saturday: "Sat"}// Calendar months names and short namesexport const CALENDAR_MONTHS = { January: "Jan", February: "Feb", March: "Mar", April: "Apr", May: "May", June: "Jun", July: "Jul", August: "Aug", September: "Sep", October: "Oct", November: "Nov", December: "Dec"}// Weeks displayed on calendarexport const CALENDAR_WEEKS = 6;// Pads a string value with leading zeroes(0) until length is reached// For example: zeroPad(5, 2) => "05"export const zeroPad = (value, length) => { return `${value}`.padStart(length, '0');}// (int) Number days in a month for a given year from 28 - 31export const getMonthDays = (month = THIS_MONTH, year = THIS_YEAR) => { const months30 = [4, 6, 9, 11]; const leapYear = year % 4 === 0; return month === 2 ? leapYear ? 29 : 28 : months30.includes(month) ? 30 : 31;}// (int) First day of the month for a given year from 1 - 7// 1 => Sunday, 7 => Saturdayexport const getMonthFirstDay = (month = THIS_MONTH, year = THIS_YEAR) => { return +(new Date(`${year}-${zeroPad(month, 2)}-01`).getDay()) + 1;}

Methods like getDay() and getMonth() in Date.prototype usually return a zero-based value. Therefore, as the first month of the year, January is 0, while December is 11. The first day of the week, Sunday, is 0, while Saturday is 7.

In the code snippet above, you’ll see that 1 was added to these zero-based values. Sunday becomes 1 for weekdays, and December becomes 12 for the month.

Lastly, notice that CALENDAR_WEEKS is set to 6; because a month typically spans four weeks, doing so allows the calendar to accommodate the last week of the previous month and the first week of the next month.

Additional helpers for verification

Let’s improve our calendar component by adding additional helper functions. First, we’ll verify the formatting of the date in the calendar module by appending the following code to the src/helpers/calendar.js file:

// (bool) Checks if a value is a date - this is just a simple checkexport const isDate = date => { const isDate = Object.prototype.toString.call(date) === '[object Date]'; const isValidDate = date && !Number.isNaN(date.valueOf()); return isDate && isValidDate;}// (bool) Checks if two date values are of the same month and yearexport const isSameMonth = (date, basedate = new Date()) => { if (!(isDate(date) && isDate(basedate))) return false; const basedateMonth = +(basedate.getMonth()) + 1; const basedateYear = basedate.getFullYear(); const dateMonth = +(date.getMonth()) + 1; const dateYear = date.getFullYear(); return (+basedateMonth === +dateMonth) && (+basedateYear === +dateYear);}// (bool) Checks if two date values are the same dayexport const isSameDay = (date, basedate = new Date()) => { if (!(isDate(date) && isDate(basedate))) return false; const basedateDate = basedate.getDate(); const basedateMonth = +(basedate.getMonth()) + 1; const basedateYear = basedate.getFullYear(); const dateDate = date.getDate(); const dateMonth = +(date.getMonth()) + 1; const dateYear = date.getFullYear(); return (+basedateDate === +dateDate) && (+basedateMonth === +dateMonth) && (+basedateYear === +dateYear);}// (string) Formats the given date as YYYY-MM-DD// Months and Days are zero paddedexport const getDateISO = (date = new Date) => { if (!isDate(date)) return null; return [ date.getFullYear(), zeroPad(+date.getMonth() + 1, 2), zeroPad(+date.getDate(), 2) ].join('-');}// ({month, year}) Gets the month and year before the given month and year// For example: getPreviousMonth(1, 2000) => {month: 12, year: 1999}// while: getPreviousMonth(12, 2000) => {month: 11, year: 2000}export const getPreviousMonth = (month, year) => { const prevMonth = (month > 1) ? month - 1 : 12; const prevMonthYear = (month > 1) ? year : year - 1; return { month: prevMonth, year: prevMonthYear };}// ({month, year}) Gets the month and year after the given month and year// For example: getNextMonth(1, 2000) => {month: 2, year: 2000}// while: getNextMonth(12, 2000) => {month: 1, year: 2001}export const getNextMonth = (month, year) => { const nextMonth = (month < 12) ? month + 1 : 1; const nextMonthYear = (month < 12) ? year : year + 1; return { month: nextMonth, year: nextMonthYear };}

Default export

Next, we’ll add the calendar builder function, which is the default export of the calendar helpers module. The calendar builder function takes a month and year as arguments, then returns an array of 42 elements, each representing a calendar date in the format [YYYY, MM,DD].

Append the code snippet below to the src/helpers/calendar.js file:

// Calendar builder for a month in the specified year// Returns an array of the calendar dates.// Each calendar date is represented as an array => [YYYY, MM, DD]export default (month = THIS_MONTH, year = THIS_YEAR) => { // Get number of days in the month and the month's first day const monthDays = getMonthDays(month, year); const monthFirstDay = getMonthFirstDay(month, year); // Get number of days to be displayed from previous and next months // These ensure a total of 42 days (6 weeks) displayed on the calendar const daysFromPrevMonth = monthFirstDay - 1; const daysFromNextMonth = (CALENDAR_WEEKS * 7) - (daysFromPrevMonth + monthDays); // Get the previous and next months and years const { month: prevMonth, year: prevMonthYear } = getPreviousMonth(month, year); const { month: nextMonth, year: nextMonthYear } = getNextMonth(month, year); // Get number of days in previous month const prevMonthDays = getMonthDays(prevMonth, prevMonthYear); // Builds dates to be displayed from previous month const prevMonthDates = [...new Array(daysFromPrevMonth)].map((n, index) => { const day = index + 1 + (prevMonthDays - daysFromPrevMonth); return [ prevMonthYear, zeroPad(prevMonth, 2), zeroPad(day, 2) ]; }); // Builds dates to be displayed from current month const thisMonthDates = [...new Array(monthDays)].map((n, index) => { const day = index + 1; return [year, zeroPad(month, 2), zeroPad(day, 2)]; }); // Builds dates to be displayed from next month const nextMonthDates = [...new Array(daysFromNextMonth)].map((n, index) => { const day = index + 1; return [nextMonthYear, zeroPad(nextMonth, 2), zeroPad(day, 2)]; }); // Combines all dates from previous, current and next months return [ ...prevMonthDates, ...thisMonthDates, ...nextMonthDates ];}

The calendar dates returned span from the date of the previous month’s final week to the following month’s first week.

Building the calendar component

Now that we have the calendar helpers module, let’s build our React calendar component. Add the following code snippet to the src/components/Calendar/index.js file:

import React, { Component, Fragment, useEffect, useState } from "react";import PropTypes from "prop-types";import * as Styled from "./styles";import calendar, { isDate, isSameDay, isSameMonth, getDateISO, getNextMonth, getPreviousMonth, WEEK_DAYS, CALENDAR_MONTHS,} from "../../helpers/calendar";export default function Calendar({ date, onDateChanged }) { const [dateState, setDateState] = useState({ current: new Date(), month: 0, year: 0, }); const [today, setToday] = useState(new Date()); useEffect(() => { addDateToState(date); }, []); const addDateToState = (date) => { const isDateObject = isDate(date); const _date = isDateObject ? date : new Date(); setDateState({ current: isDateObject ? date : null, month: +_date.getMonth() + 1, year: _date.getFullYear(), }); }; const getCalendarDates = () => { const { current, month, year } = dateState; console.log(typeof current); console.log(current); const calendarMonth = month || +current?.getMonth() + 1; const calendarYear = year || current?.getFullYear(); return calendar(calendarMonth, calendarYear); }; return ( <Styled.CalendarContainer> {renderMonthAndYear()} <Styled.CalendarGrid> <Fragment>{Object.keys(WEEK_DAYS).map(renderDayLabel)}</Fragment> <Fragment>{getCalendarDates().map(renderCalendarDate)}</Fragment> </Styled.CalendarGrid> </Styled.CalendarContainer> );}Calendar.propTypes = { date: PropTypes.instanceOf(Date), onDateChanged: PropTypes.func,};

The default calendar export, as well as other helper functions and constants, have been imported from the calendar helpers module. All of the exports from the calendar styles module have been imported with the Styled namespace.

At the moment, we haven’t created any styles, but we will soon, using styled-components. The component state is partly resolved from props using the useState Hook, which sets the state with an object containing the following:

  • current: A Date object for the currently selected date or null
  • month: The month of the currently selected date if it is set. Otherwise, it defaults to the month of the current date
  • year: The year of the currently selected date if it is set. Otherwise, it defaults to the year of the current date

The month and year state properties are required to properly render the calendar, as shown in the getCalendarDates() method, which uses the calendar builder function to build the calendar for the month and year.

Over 200k developers use LogRocket to create better digital experiencesLearn more →

Finally, the state is augmented with the today property, which is a Date object for the current date.

Rendering parts of the calendar component

In the previous calendar component code snippet, the renderer part referenced methods for rendering the month and year, weekdays, and calendar dates. We’ll add these methods to the calendar component below the getCalendarDates() function as follows:

const renderMonthAndYear = () => { const { month, year } = dateState; const formatter = new Intl.DateTimeFormat('zh-CN', { day: 'numeric', month: 'short', year: 'numeric' }); const formattedDate = formatter.format(dateState.current); console.log('formatted date', formattedDate) // Resolve the month name from the CALENDAR_MONTHS object map const monthname = Object.keys(CALENDAR_MONTHS)[Math.max(0, Math.min(month - 1, 11))]; return ( <Styled.CalendarHeader> <Styled.ArrowLeft onClick={handlePrevious} title="Previous Month" /> <Styled.CalendarMonth> {monthname} {year} {/* {formattedDate} */} </Styled.CalendarMonth> <Styled.ArrowRight onClick={handleNext} title="Next Month" /> </Styled.CalendarHeader> );};// Render the label for day of the week// This method is used as a map callback as seen in render()const renderDayLabel = (day, index) => { // Resolve the day of the week label from the WEEK_DAYS object map const daylabel = WEEK_DAYS[day].toUpperCase(); return ( <Styled.CalendarDay key={daylabel} index={index}> {daylabel} </Styled.CalendarDay> );};// Render a calendar date as returned from the calendar builder functionconst renderCalendarDate = (date, index) => { const { current, month, year } = dateState; const _date = new Date(date.join("-")); // Check if calendar date is same day as today const isToday = isSameDay(_date, today); // Check if calendar date is same day as currently selected date const isCurrent = current && isSameDay(_date, current); // Check if calendar date is in the same month as the state month and year const inMonth = month && year && isSameMonth(_date, new Date([year, month, 1].join("-"))); // The click handler const onClick = gotoDate(_date); const props = { index, inMonth, onClick, title: _date.toDateString() }; // Conditionally render a styled date component const DateComponent = isCurrent ? Styled.HighlightedCalendarDate : isToday ? Styled.TodayCalendarDate : Styled.CalendarDate; return ( <DateComponent key={getDateISO(_date)} {...props}> {_date.getDate()} {/* {formattedDate} */} </DateComponent> );};

In the renderMonthAndYear() method, the name of the month is first resolved from the CALENDAR_MONTHS object. Then, it is rendered alongside the year with two arrow controls on the left and right side for navigating.

Each arrow control has event handlers for the onClick events, which we’ll define later as handlePrevious() and handleNext(). With some styling added, the rendered DOM from the renderMonthAndYear() method looks like the following screenshot:

Creating a custom React datepicker - LogRocket Blog (6)

The renderDayLabel() method renders a label for a day of the week, which it resolves from the WEEK_DAYS object. It takes two arguments,  day and index, because it is used as a callback function to .map(), as seen in the render() method.

After mapping, the rendered DOM for the days of the week looks like the image below:

Creating a custom React datepicker - LogRocket Blog (7)

The renderCalendarDate() method is also used as a .map() callback function and renders a calendar date. The date it receives as its first argument is in the format [YYYY, MM, DD].

The renderCalendarDate() method checks if the date is the same as today’s date, including the current state’s month and year. With these checks, it conditionally renders one of the variants of the calendar date cell, HiglightedCalendarDate, TodayCalendarDate, or CalendarDate.

For each rendered calendar date, an onClick handler is set to jump to that particular date using the gotoDate() method, which we’ll define in the next section.

Event handlers

We’ve made a couple of references to event handlers in the previous sections. Add the following code below the renderCalendarDate function located inside the calendar component:

const gotoDate = (date) => (evt) => { evt && evt.preventDefault(); const { current } = dateState; if (!(current && isSameDay(date, current))) { addDateToState(date); onDateChanged(date); }};const gotoPreviousMonth = () => { const { month, year } = dateState; const previousMonth = getPreviousMonth(month, year); setDateState({ month: previousMonth.month, year: previousMonth.year, current: dateState.current, });};const gotoNextMonth = () => { const { month, year } = dateState; const nextMonth = getNextMonth(month, year); setDateState({ month: nextMonth.month, year: nextMonth.year, current: dateState.current, });};const handlePrevious = (evt) => { gotoPreviousMonth();};const handleNext = (evt) => { gotoNextMonth();};

The gotoDate() method is a higher-order function that takes a Date object as its argument. It returns an event handler that can be triggered to update the currently selected date in the state. addStateToDate() is used to resolve the month and year from the date and update the state.

If a callback function is passed to the onDateChanged prop of the calendar component, that function will be called with the updated date. This feature is especially helpful in cases where you want to propagate the date change to a parent component.

The handlePrevious() and handleNext() event handlers share a similar behavior. They allow the user to cycle through the months.

Styling the calendar

With our calendar component complete, let’s add styled-components for styling. Add the following code snippet to the src/components/Calendar/styles.js file:

import styled from "styled-components";export const Arrow = styled.button` appearance: none; user-select: none; outline: none !important; display: inline-block; position: relative; cursor: pointer; padding: 0; border: none; border-top: 1.6em solid transparent; border-bottom: 1.6em solid transparent; transition: all 0.25s ease-out;`;export const ArrowLeft = styled(Arrow)` border-right: 2.4em solid #ccc; left: 1.5rem; :hover { border-right-color: #06c; }`;export const ArrowRight = styled(Arrow)` border-left: 2.4em solid #ccc; right: 1.5rem; :hover { border-left-color: #06c; }`;export const CalendarContainer = styled.div` font-size: 5px; border: 2px solid #06c; border-radius: 5px; overflow: hidden;`;export const CalendarHeader = styled.div` display: flex; align-items: center; justify-content: space-between;`;export const CalendarGrid = styled.div` display: grid; grid-template: repeat(7, auto) / repeat(7, auto);`;export const CalendarMonth = styled.div` font-weight: 500; font-size: 5em; color: #06c; text-align: center; padding: 0.5em 0.25em; word-spacing: 5px; user-select: none;`;export const CalendarCell = styled.div` text-align: center; align-self: center; letter-spacing: 0.1rem; padding: 0.6em 0.25em; user-select: none; grid-column: ${(props) => (props.index % 7) + 1} / span 1;`;export const CalendarDay = styled(CalendarCell)` font-weight: 600; font-size: 2.25em; color: #06c; border-top: 2px solid #06c; border-bottom: 2px solid #06c; border-right: ${(props) => (props.index % 7) + 1 === 7 ? `none` : `2px solid #06c`};`;export const CalendarDate = styled(CalendarCell)` font-weight: ${(props) => (props.inMonth ? 500 : 300)}; font-size: 4em; cursor: pointer; border-bottom: ${(props) => (props.index + 1) / 7 <= 5 ? `1px solid #ddd` : `none`}; border-right: ${(props) => (props.index % 7) + 1 === 7 ? `none` : `1px solid #ddd`}; color: ${(props) => (props.inMonth ? `#333` : `#ddd`)}; grid-row: ${(props) => Math.floor(props.index / 7) + 2} / span 1; transition: all 0.4s ease-out; :hover { color: #06c; background: rgba(0, 102, 204, 0.075); }`;export const HighlightedCalendarDate = styled(CalendarDate)` color: #fff !important; background: #06c !important; position: relative; ::before { content: ""; position: absolute; top: -1px; left: -1px; width: calc(100% + 2px); height: calc(100% + 2px); border: 2px solid #06c; }`;export const TodayCalendarDate = styled(HighlightedCalendarDate)` color: #06c !important; background: transparent !important; ::after { content: ""; position: absolute; right: 0; bottom: 0; border-bottom: 0.75em solid #06c; border-left: 0.75em solid transparent; border-top: 0.75em solid transparent; } :hover { color: #06c !important; background: rgba(0, 102, 204, 0.075) !important; }`;export const BlockedCalendarDate = styled(CalendarDate)` color: black !important; background: gray !important; position: relative; :hover { color: black !important; background: gray !important; border-color:gray; cursor:default; }`;

Now, when you render the calendar component in the app, it will look like the screenshot below:

Creating a custom React datepicker - LogRocket Blog (8)

Custom React datepicker component

To begin building our datepicker component, add the following code snippet to the src/components/Datepicker/index.js file:

import React, { useState, useEffect } from "react";import PropTypes from "prop-types";import Calendar from "../Calendar/index";import * as Styled from "./styles";import { isDate, getDateISO } from "../../helpers/calendar";export default function Datepicker(props) { const [dateState, setDateState] = useState(); const [calendarOpen, setCalendarOpen] = useState(false); const { label } = props; const toggleCalendar = () => setCalendarOpen(!calendarOpen); const handleChange = (evt) => evt.preventDefault(); const handleDateChange = (date) => { const newDate = date ? getDateISO(date) : null; dateState !== newDate && setDateState(newDate) }; useEffect(() => { const newDate = new Date(); console.log('new date', newDate) setDateState(getDateISO(newDate)); }, []); const closeCalendar = () => { setCalendarOpen(false) }}Datepicker.propTypes = { label: PropTypes.string, value: PropTypes.string, onDateChanged: PropTypes.func,};

The component state is initialized with two properties:

  • date: An ISO string representation for the current date, formatted as "YYYY-MM-DD"
  • calendarOpen: A Boolean flag that indicates whether the datepicker calendar is visible

When the component mounts, the Date object is resolved from the value prop passed to the component, then it is updated on the state, as seen in the useEffect function.

The handleDateChange method takes a Date object as its argument and updates the date in the state. If a callback function is passed to the onDateChanged prop of the datepicker component, then that function will be called with the updated ISO date string.

Rendering the datepicker

It’s worth mentioning that the Bootstrap dropdown component will be used to simulate the dropdown effect for our custom datepicker, which is the primary purpose of adding the Reactstrap package as a dependency in our project. The styled components rendered in the datepicker are styled extensions of the dropdown components from Reactstrap.

Update the datepicker component to include the render() method, as shown in the following code snippet:

return ( <Styled.DatePickerContainer > <Styled.DatePickerFormGroup> <Styled.DatePickerLabel>{label || "Enter Date"}</Styled.DatePickerLabel> <Styled.DatePickerInput type="text" value={dateState ? dateState.split("-").join(" / ") : ""} onChange={handleChange} readOnly="readonly" placeholder="YYYY / MM / DD" /> </Styled.DatePickerFormGroup> <Styled.DatePickerDropdown isOpen={calendarOpen} toggle={toggleCalendar}> <Styled.DatePickerDropdownToggle color="transparent" /> <Styled.DatePickerDropdownMenu> {calendarOpen && ( <div> <Calendar date={dateState && new Date(dateState)} onDateChanged={handleDateChange} /> <Styled.DatePickerButton onClick={closeCalendar}>Save</Styled.DatePickerButton> </div> )} </Styled.DatePickerDropdownMenu> </Styled.DatePickerDropdown> </Styled.DatePickerContainer>);

The Styled.DatePickerFormGroup component is a Bootstrap .form-group that wraps the datepicker label and input field. It’s important to note that the input field is the "text" type and marked as readonly, so you can’t edit it directly. Also, notice that the default behavior for the change event on the input element has been prevented.

The Styled.DatePickerDropdown component and its descendants are styled extensions of the dropdown component from the Reactstrap package.

Finally, the calendar component is rendered in the dropdown menu, passing the date from the state and the handleDateChange() method as a callback function for the onDateChanged prop.

The final rendered DOM for the datepicker component should look like the following screenshot, with some styling:

Creating a custom React datepicker - LogRocket Blog (9)

Styling the datepicker

Add the following code snippet to the src/components/Datepicker/styles.js file to create the styled components required for the datepicker:

import styled from "styled-components";import { FormGroup, Label, Input, Button, Dropdown, DropdownToggle, DropdownMenu,} from "reactstrap";export const DatePickerContainer = styled.div` position: relative;`;export const DatePickerFormGroup = styled(FormGroup)` display: flex; justify-content: space-between; position: relative; width: 100%; border: 2px solid #06c; border-radius: 5px; overflow: hidden;`;export const DatePickerLabel = styled(Label)` margin: 0; padding: 0 2rem; font-weight: 600; font-size: 0.7rem; letter-spacing: 2px; text-transform: uppercase; color: #06c; border-right: 2px solid #06c; display: flex; align-items: center; justify-content: center; background: rgba(0, 102, 204, 0.05);`;export const DatePickerInput = styled(Input)` font-weight: 500; font-size: 1rem; color: #333; box-shadow: none; border: none; text-align: center; letter-spacing: 1px; background: transparent !important; display: flex; align-items: center; ::placeholder { color: #999; font-size: 0.9rem; } width:100%; height:100%;`;export const DatePickerDropdown = styled(Dropdown)` position: absolute; width: 100%; height: 100%; top: 0; left: 0;`;export const DatePickerDropdownToggle = styled(DropdownToggle)` position: relative; width: 100%; height: 100%; background: transparent; opacity: 0; filter: alpha(opacity=0);`;export const DatePickerDropdownMenu = styled(DropdownMenu)` margin-top: 3rem; left: 0; width: 100%; height: 75vh !important; border: none; padding: 0; transform: none !important;`;export const DatePickerButton = styled(Button)` position: absolute; border: 2px solid #06c; margin-top:2%; right:50% !important; background: transparent; font-size: 1.2rem; color: #06c; :hover { border: white solid #06c; color: white !important; background: #06c; }`;

The app component

Finally, update the src/App.js file to look like the following code snippet:

import "./App.css";function App() { return ( <div className="App container"> <div className="mt-2 w-100 h-50 d-flex justify-content-center row"> <div className="col-6"> </div> </div> </div> );}export default App;

If you have followed this tutorial, you should have a working, custom datepicker rendered in your React application. Now, render it inside the app component:

import "./App.css";import Datepicker from "./components/Datepicker";function App() { return ( <div className="App container"> <div className="mt-2 w-100 h-50 d-flex justify-content-center row"> <div className="col-6"> <Datepicker label="From" /> </div> </div> </div> );}export default App;

Here’s our final result:

Creating a custom React datepicker - LogRocket Blog (10)

Customizing our datepicker with advanced features

In this section, we will add a few advanced features to our custom datepicker such as date range selection, a time picker, and the ability to block certain dates in the calendar.

Date range selection

To add a date range selection feature, we will use just two Datepicker components. Go back to your App.js file and replace the App component code with the following:

function App() { return ( <div className="App container"> <div className="mt-2 w-100 h-50 d-flex justify-content-center row"> <div className="col"> <Datepicker label="From" /> </div> <div className="col"> <Datepicker label="To" /> </div> </div> </div> );}

Here, you added two Datepicker components instead of one to the App component with the labels From and To:

Creating a custom React datepicker - LogRocket Blog (11)

Time picker

To add the time picker feature, you will first need to create a directory named TimePicker inside the Components directory:

mkdir -p src/components/Timepicker

Next, inside this directory, create two files named index.js and styles.js:

touch src/components/Timepicker/index.js src/components/Timepicker/styles.js

Add the following code to the src/components/Timepicker/index.js file:

import React, { useState } from "react";import PropTypes from "prop-types";import { ListGroup, ListGroupItem,} from "reactstrap";import * as Styled from "./styles";const hours = Array.from({ length: 12 }, (_, i) => i + 1);const minutes = Array.from({ length: 60 }, (_, i) => i);const dayPeriods = ["AM", "PM"];export default function TimePicker({ handleTimeChange }) { const [timeState, setTimeState] = useState({ hour: 12, minutes: 0, period: 'AM', }); const handleHourChange = (event, hour) => { console.log('hour change', hour) const newState = { hour: hour, minutes: timeState.minutes, period: timeState.period, } setTimeState(newState) setTimeout(() => { handleTimeChange(newState) }, 100); } const handleMinutesChange = (event, minutes) => { console.log('minutes change', minutes) const newState = { hour: timeState.hour, minutes: minutes, period: timeState.period, } setTimeState(newState) handleTimeChange(newState) } const handlePeriodChange = (event, period) => { console.log('minutes change', period) const newState = { hour: timeState.hour, minutes: timeState.minutes, period: period, } setTimeState(newState) handleTimeChange(newState) } return ( <div className="d-flex flex-row"> <Styled.TimePickerListContainer className="border-end border-primary "> <ListGroup className="" > {hours.map((hour, index) => ( <ListGroupItem key={index} tag="button" action className="border-0" onClick={(event) => handleHourChange(event, hour)}>{hour}</ListGroupItem> ))} </ListGroup> </Styled.TimePickerListContainer> <Styled.TimePickerListContainer className="border-end border-primary"> <ListGroup className="" > {minutes.map((minute, index) => ( <ListGroupItem key={index} tag="button" action className="border-0" onClick={(event) => handleMinutesChange(event, minute)}>{minute}</ListGroupItem> ))} </ListGroup> </Styled.TimePickerListContainer> <Styled.TimePickerListContainer className=""> <ListGroup className="" > {dayPeriods.map((period, index) => ( <ListGroupItem key={index} tag="button" action className="border-0 pe-4" onClick={(event) => handlePeriodChange(event, period)}>{period}</ListGroupItem> ))} </ListGroup> </Styled.TimePickerListContainer> </div > )}TimePicker.propTypes = { handleTimeChange: PropTypes.func,};

This TimePicker component above is designed to allow users to select a time, providing options for hours, minutes, and the period (AM or PM). It uses React’s useState Hook to manage the selected time state and PropTypes for ensuring proper type checking of props.

The component expects a handleTimeChange prop, which is a function responsible for updating the selected time state in the parent component whenever a change occurs. This function is crucial for keeping the parent component in sync with the TimePicker‘s state.

To populate the selectable options, the component generates arrays for hours (1-12), minutes (0-59), and periods (AM and PM) using Array.from. These arrays are then mapped to create ListGroupItems, which are styled to resemble buttons. Each button is clickable and triggers the respective event handler (handleHourChange, handleMinutesChange, handlePeriodChange) when clicked.

The event handlers update the selected time state based on the user’s selection, ensuring that the displayed time accurately reflects the user’s choice. Additionally, a timeout is used to delay the handleTimeChange function call to prevent the rapid firing of events while the user is selecting a time.

Let’s add a single styled component for styling. Add the following code snippet to the src/components/Timepicker/styles.js file:

import styled from "styled-components";export const TimePickerListContainer = styled.div` height: 75vh !important; overflow-y: auto;`;

Now it’s time to add the Timepicker component inside the Datepicker component. Open the src/components/Datepicker/index.js file and import Timepicker:

import TimePicker from "../Timepicker";

Next, add the following code below the label constant located inside Datepicker:

const [timeState, setTimeState] = useState({ hour: 12, minutes: 0, period: 'AM',});const handleTimeChange = (time) => { const newTime = time ? time : null; if (newTime !== null && JSON.stringify(timeState) !== JSON.stringify(newTime)) { setTimeState(newTime) }};const formatTime = (time) => { const hour = time.hour < 10 ? '0' + time.hour : time.hour const minutes = time.minutes < 10 ? '0' + time.minutes : time.minutes const formattedTime = hour + ' : ' + minutes + ' ' + time.period return formattedTime}

This code snippet sets up a timeState state variable using React’s useState Hook. The timeState object contains three properties: hour, minutes, and period, initialized to 12, 0, and AM respectively. This state variable will hold the currently selected time.

Additionally, two functions named handleTimeChange and formatTime were defined.

The handleTimeChange function takes a time parameter and is called inside the Timepicker component. This function is responsible for updating the timeState based on the new time value. It first checks if the time parameter is not null and if the new time is different from the current timeState. If these conditions are met, it updates timeState using setTimeState.

The formatTime function also takes a time parameter, and it will be called inside the render function to format the time.

Now, go to the render function and update it to include the Timepicker:

return ( <Styled.DatePickerContainer > <Styled.DatePickerFormGroup> <Styled.DatePickerLabel>{label || "Enter Date"}</Styled.DatePickerLabel> <Styled.DatePickerInput type="text" value={dateState ? dateState.split("-").join(" / ") : ""} onChange={handleChange} readOnly="readonly" placeholder="YYYY / MM / DD" /> <Styled.DatePickerInput type="text" value={timeState ? formatTime(timeState) : ""} onChange={handleChange} readOnly="readonly" placeholder="12 : 00 AM" /> </Styled.DatePickerFormGroup> <Styled.DatePickerDropdown isOpen={calendarOpen} toggle={toggleCalendar}> <Styled.DatePickerDropdownToggle color="transparent" /> <Styled.DatePickerDropdownMenu> {calendarOpen && ( <div > <div className="row"> <div className="col-9"> <Calendar date={dateState && new Date(dateState)} onDateChanged={handleDateChange} /> </div> <div className="col-3 pe-4"> <TimePicker handleTimeChange={handleTimeChange} className="border border-primary"/> </div> </div> <Styled.DatePickerButton onClick={closeCalendar}>Save</Styled.DatePickerButton> </div> )} </Styled.DatePickerDropdownMenu> </Styled.DatePickerDropdown> </Styled.DatePickerContainer>);

After the update, the component now renders two input fields, one input field for the date and another for the time. The selected date is displayed in the date input field. The time input field displays the selected time, which can be changed using the TimePicker component.

The TimePicker component was added below the Calendar component and it receives the handleTimeChange function as a prop. Here is the adjusted datepicker with the time picker option:

Creating a custom React datepicker - LogRocket Blog (12)

Blocking certain dates

To block certain dates from being selected in the calendar, you will need to make a couple of changes to the Datepicker and Calendar components.

Open the src/components/Datepicker/index.js file and add the following code below the closeCalendar function:

const blockedDates = [ new Date('2024-04-7'), new Date('2024-04-13'), new Date('2024-04-15'), new Date('2024-04-20')];

Above, you added an array containing the dates you’d like to block in the calendar.

Next, go to the render function and pass the blockedDates to the Calendar component as a prop:

<Calendar date={dateState && new Date(dateState)} onDateChanged={handleDateChange} blockedDates={blockedDates}/>

Now, open the src/components/Calendar/index.js file and update it with the following code:

export default function Calendar({ date, onDateChanged, blockedDates }) { ... const renderCalendarDate = (date, index) => { const { current, month, year } = dateState; const _date = new Date(date.join("-")); // Check if calendar date is same day as today const isToday = isSameDay(_date, today); // block date const isBlocked = blockedDates.some(blockedDate => isSameDay(_date, blockedDate)); // Check if calendar date is same day as currently selected date const isCurrent = current && isSameDay(_date, current); // Check if calendar date is in the same month as the state month and year const inMonth = month && year && isSameMonth(_date, new Date([year, month, 1].join("-"))); // The click handler const onClick = isBlocked ? null : gotoDate(_date); const props = { index, inMonth, onClick, title: _date.toDateString() }; // Conditionally render a styled date component const DateComponent = isCurrent ? Styled.HighlightedCalendarDate : isToday ? Styled.TodayCalendarDate : !isBlocked ? Styled.CalendarDate : Styled.BlockedCalendarDate; return ( <DateComponent key={getDateISO(_date)} {...props}> {_date.getDate()} {/* {formattedDate} */} </DateComponent> ); };Calendar.propTypes = { date: PropTypes.instanceOf(Date), onDateChanged: PropTypes.func, blockedDates: PropTypes.arrayOf(Date)};

In the updated code, first, blockedDates was added as a prop to the Calendar component.

Next, the code modifies the renderCalendarDate function to also check if the date that has to be rendered is included in blockedDates. If that is the case, the BlockedCalendarDate styled component will be rendered to indicate that the date is blocked and a null onClick event listener will be assigned to it, preventing the user from selecting the date.

Lastly, blockedDates was added to the Calendar PropTypes:

Creating a custom React datepicker - LogRocket Blog (13)

You saw firsthand that implementing a custom datepicker from scratch can be a complex task, but luckily that isn’t the only option. In this section, we will try some pre-made datepicker components.

React Date Picker

React Date Picker is a popular component that allows users to select dates easily in a web application. It provides a visual calendar interface for selecting dates and can be customized to fit the look and feel of the application.

Run one of the following commands to install the component if you are using npm or Yarn:

npm install react-datepicker
yarn add react-datepicker

To use this component in your application, you will need to import the component and the CSS associated with this package.

Here is what your App.js file should look like:

import "./App.css";import React, { useState } from "react";import DatePicker from "react-datepicker";import "react-datepicker/dist/react-datepicker.css";function App() { const [startDate, setStartDate] = useState(new Date()); return ( <div className="mt-2 w-100 h-50 d-flex justify-content-center"> <DatePicker selected={startDate} onChange={(date) => setStartDate(date)} /> </div> );}export default App;

Creating a custom React datepicker - LogRocket Blog (14)

Let’s add the ability for a user to select dates within a specific range:

function App() { const [startDate, setStartDate] = useState(new Date()); const [endDate, setEndDate] = useState(null); const onChange = (dates) => { const [start, end] = dates; setStartDate(start); setEndDate(end); }; return ( <div className="mt-2 w-100 h-50 d-flex justify-content-center"> <DatePicker selected={startDate} onChange={onChange} startDate={startDate} endDate={endDate} selectsRange inline /> </div> );}

Creating a custom React datepicker - LogRocket Blog (15)

MUI Date and Time Pickers

MUI is a freemium React component library that provides ready-to-use components following Google’s Material Design guidelines.

Run the following commands to install the MUI Date and Time Pickers library if you are using npm or Yarn:

npm install @mui/x-date-pickers @mui/material @emotion/react @emotion/styled// Install date library (if not already installed)npm install dayjsyarn add @mui/x-date-pickers @mui/material @emotion/react @emotion/styled// Install date library (if not already installed)yarn add dayjs

Before rendering any components that require date or time selection, the MUI documentation recommends placing a LocalizationProvider higher in the React component tree. This component is responsible for integrating your selected date library adapterAdapterDayjs, which leverages Day.js — with the Date and Time Picker components throughout your application by using the React Context system. This setup ensures that date and time functionality is uniformly available across your UI components.

Here is what your App.js file should look like:

import "./App.css";import { LocalizationProvider } from '@mui/x-date-pickers';import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'import { DatePicker } from '@mui/x-date-pickers/DatePicker';function App() { return ( <LocalizationProvider dateAdapter={AdapterDayjs}> <div className="mt-2 w-100 h-50 d-flex justify-content-center"> <DatePicker /> </div> </LocalizationProvider> );}export default App;

Creating a custom React datepicker - LogRocket Blog (16)

This library’s date and time picker components are available for free but the date range components are only available on the paid Pro plan.

Conclusion

In this tutorial, we covered how to set up a datepicker in React that acts as an alternative to the native HTML5 datepicker input element. With a straightforward datepicker, your users are less likely to format date inputs incorrectly, improving the quality of the information provided to your application.

You can customize and upgrade the datepicker from this tutorial to best suit your application. I hope you enjoyed this article. Happy coding!

Get set up with LogRocket's modern React error tracking in minutes:

  1. Visit https://logrocket.com/signup/ to getan app ID
  2. Install LogRocket via npm or script tag. LogRocket.init() must be called client-side, notserver-side

    • npm
    • Script tag
    $ npm i --save logrocket // Code:import LogRocket from 'logrocket'; LogRocket.init('app/id'); 
    // Add to your HTML:<script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script><script>window.LogRocket && window.LogRocket.init('app/id');</script> 
  3. (Optional) Install plugins for deeper integrations with your stack:
    • Redux middleware
    • NgRx middleware
    • Vuex plugin

Get started now

Creating a custom React datepicker - LogRocket Blog (2024)
Top Articles
Pros and Cons of Bitwarden 2024
Key Lifecycle for AES 256 Encryption (Bitlocker)
English Bulldog Puppies For Sale Under 1000 In Florida
Katie Pavlich Bikini Photos
Gamevault Agent
Pieology Nutrition Calculator Mobile
Hocus Pocus Showtimes Near Harkins Theatres Yuma Palms 14
Hendersonville (Tennessee) – Travel guide at Wikivoyage
Compare the Samsung Galaxy S24 - 256GB - Cobalt Violet vs Apple iPhone 16 Pro - 128GB - Desert Titanium | AT&T
Vardis Olive Garden (Georgioupolis, Kreta) ✈️ inkl. Flug buchen
Craigslist Dog Kennels For Sale
Things To Do In Atlanta Tomorrow Night
Non Sequitur
Crossword Nexus Solver
How To Cut Eelgrass Grounded
Pac Man Deviantart
Alexander Funeral Home Gallatin Obituaries
Energy Healing Conference Utah
Geometry Review Quiz 5 Answer Key
Hobby Stores Near Me Now
Icivics The Electoral Process Answer Key
Allybearloves
Bible Gateway passage: Revelation 3 - New Living Translation
Yisd Home Access Center
Home
Shadbase Get Out Of Jail
Gina Wilson Angle Addition Postulate
Celina Powell Lil Meech Video: A Controversial Encounter Shakes Social Media - Video Reddit Trend
Walmart Pharmacy Near Me Open
Marquette Gas Prices
A Christmas Horse - Alison Senxation
Ou Football Brainiacs
Access a Shared Resource | Computing for Arts + Sciences
Vera Bradley Factory Outlet Sunbury Products
Pixel Combat Unblocked
Movies - EPIC Theatres
Cvs Sport Physicals
Mercedes W204 Belt Diagram
Mia Malkova Bio, Net Worth, Age & More - Magzica
'Conan Exiles' 3.0 Guide: How To Unlock Spells And Sorcery
Teenbeautyfitness
Where Can I Cash A Huntington National Bank Check
Topos De Bolos Engraçados
Sand Castle Parents Guide
Gregory (Five Nights at Freddy's)
Grand Valley State University Library Hours
Holzer Athena Portal
Hello – Cornerstone Chapel
Stoughton Commuter Rail Schedule
Nfsd Web Portal
Selly Medaline
Latest Posts
Article information

Author: Clemencia Bogisich Ret

Last Updated:

Views: 6064

Rating: 5 / 5 (60 voted)

Reviews: 83% of readers found this page helpful

Author information

Name: Clemencia Bogisich Ret

Birthday: 2001-07-17

Address: Suite 794 53887 Geri Spring, West Cristentown, KY 54855

Phone: +5934435460663

Job: Central Hospitality Director

Hobby: Yoga, Electronics, Rafting, Lockpicking, Inline skating, Puzzles, scrapbook

Introduction: My name is Clemencia Bogisich Ret, I am a super, outstanding, graceful, friendly, vast, comfortable, agreeable person who loves writing and wants to share my knowledge and understanding with you.