Building a Datepicker in React

Building a Datepicker in React: A Comprehensive Guide

Building a datepicker from scratch in React can seem daunting, but it’s a fantastic exercise in understanding controlled components, state management, and date manipulation. This guide breaks down the process into manageable steps, providing code examples and explanations along the way. We’ll build a functional, accessible datepicker without relying on external libraries (although libraries like react-datepicker are excellent for production use).

1. Project Setup (Optional):

If you don’t have a React project already, you can create one using Create React App:

bash
npx create-react-app my-datepicker
cd my-datepicker
npm start

2. Component Structure:

We’ll create a Datepicker component that handles the following:

  • Displaying the current month.
  • Navigating between months (and optionally, years).
  • Selecting a date.
  • Handling input (optional, for direct date entry).
  • Managing state (selected date, current month/year).

Create a new file Datepicker.js (or .jsx):

“`javascript
import React, { useState } from ‘react’;
import ‘./Datepicker.css’; // We’ll create this later

function Datepicker() {
// State variables (explained below)
const [selectedDate, setSelectedDate] = useState(null);
const [currentDate, setCurrentDate] = useState(new Date()); // Start with today’s date
const [showCalendar, setShowCalendar] = useState(false);
const [inputValue, setInputValue] = useState(”);

// Helper functions (explained below)
const getDaysInMonth = (year, month) => {
    return new Date(year, month + 1, 0).getDate();
};

const getFirstDayOfMonth = (year, month) => {
    return new Date(year, month, 1).getDay();
};

const handleDateClick = (day) => {
  const newDate = new Date(currentDate.getFullYear(), currentDate.getMonth(), day);
  setSelectedDate(newDate);
  setShowCalendar(false); // Hide the calendar after selection
  setInputValue(formatDate(newDate));
};

const handlePrevMonth = () => {
    setCurrentDate(prevDate => {
        const newMonth = prevDate.getMonth() - 1;
        return new Date(prevDate.getFullYear(), newMonth, 1);
    });
};

const handleNextMonth = () => {
    setCurrentDate(prevDate => {
        const newMonth = prevDate.getMonth() + 1;
        return new Date(prevDate.getFullYear(), newMonth, 1);
    });
};
const handleInputChange = (e) => {
    setInputValue(e.target.value);
    // Basic validation and parsing (you can improve this)
    const parsedDate = parseDate(e.target.value);
    if (parsedDate) {
        setSelectedDate(parsedDate);
        setCurrentDate(parsedDate);
    } else {
      setSelectedDate(null); // Clear selection if input is invalid
    }
};

const toggleCalendar = () => {
  setShowCalendar(!showCalendar);
};

const formatDate = (date) => {
  if (!date) return '';
  const year = date.getFullYear();
  const month = String(date.getMonth() + 1).padStart(2, '0'); // Add leading zero
  const day = String(date.getDate()).padStart(2, '0');
  return `${year}-${month}-${day}`;

};

const parseDate = (str) => {
const parts = str.split(‘-‘);
if (parts.length === 3) {
const year = parseInt(parts[0], 10);
const month = parseInt(parts[1], 10) – 1; // Month is 0-indexed
const day = parseInt(parts[2], 10);

    if (!isNaN(year) && !isNaN(month) && !isNaN(day)) {
        const date = new Date(year, month, day);
        // Check for valid date (e.g., Feb 30th)
        if (date.getFullYear() === year && date.getMonth() === month && date.getDate() === day) {
            return date;
        }
    }
}
return null;

};

// ... (rest of the component)

}
export default Datepicker
“`

3. State Variables and Helper Functions:

Let’s break down the state and helper functions:

  • selectedDate (useState(null)): Holds the currently selected date. Initialized to null (no date selected).
  • currentDate (useState(new Date())): Represents the currently displayed month and year. Initialized to today’s date. This is different from the selected date. It controls what’s shown in the calendar.
  • showCalendar (useState(false)): Controls whether the calendar is visible.
  • inputValue (useState(”)): Stores the value of the input field.
  • getDaysInMonth(year, month): Calculates the number of days in a given month and year. Crucially handles leap years.
  • getFirstDayOfMonth(year, month): Gets the day of the week (0 for Sunday, 1 for Monday, etc.) for the first day of the month. This is used to correctly position the days in the calendar grid.
  • handleDateClick(day): Updates selectedDate when a day is clicked, hides calendar, and updates input field.
  • handlePrevMonth() and handleNextMonth(): Updates currentDate to the previous or next month.
  • handleInputChange(e): Updates the input value and attempts to parse it into a date.
  • toggleCalendar(): Toggles the visibility of the calendar.
  • formatDate(date): Formats a Date object into a ‘YYYY-MM-DD’ string.
  • parseDate(str): Parses a ‘YYYY-MM-DD’ string into a Date object, with basic validation.

4. Rendering the Calendar:

Now, let’s add the JSX to render the calendar:

“`javascript
// Inside the Datepicker component, add the return statement:

return (


{showCalendar && (


{currentDate.toLocaleString(‘default’, { month: ‘long’, year: ‘numeric’ })}
Sun
Mon
Tue
Wed
Thu
Fri
Sat

{/ Render days here (explained below) /}
{Array.from({ length: getFirstDayOfMonth(currentDate.getFullYear(), currentDate.getMonth()) }).map((, index) => (

empty-${index}} className=”calendar-day empty”>

))}
{Array.from({ length: getDaysInMonth(currentDate.getFullYear(), currentDate.getMonth()) }).map((
, index) => {
const day = index + 1;
const isSelected = selectedDate &&
selectedDate.getFullYear() === currentDate.getFullYear() &&
selectedDate.getMonth() === currentDate.getMonth() &&
selectedDate.getDate() === day;
const isToday = new Date().getFullYear() === currentDate.getFullYear() &&
new Date().getMonth() === currentDate.getMonth() &&
new Date().getDate() === day;
return (

calendar-day ${isSelected ? ‘selected’ : ”} ${isToday ? ‘today’ : ”}}
onClick={() => handleDateClick(day)}
>
{day}

);
})}

)}

);

“`

Explanation of the JSX:

  • Input Field: A text input to display and (optionally) edit the selected date. The onClick handler toggles the calendar visibility. onChange handles manual date entry.
  • Conditional Rendering ({showCalendar && ...}): The calendar is only rendered if showCalendar is true.
  • Calendar Header: Displays the current month and year, with buttons to navigate to the previous and next months.
  • Calendar Weekdays: Displays the abbreviations for the days of the week.
  • Calendar Days: This is the core of the calendar. It uses two Array.from calls:
    • The first creates empty div elements to fill in the spaces before the first day of the month, based on getFirstDayOfMonth.
    • The second creates a div for each day of the month.
    • isSelected and isToday Classes: These classes are added conditionally to highlight the selected date and today’s date, respectively. We’ll use CSS to style these.

5. CSS Styling (Datepicker.css):

Create Datepicker.css and add the following styles:

“`css
.datepicker {
position: relative; / Important for absolute positioning of the calendar /
display: inline-block; / Or block, depending on your layout /
}

.calendar {
position: absolute;
top: 100%; / Position below the input /
left: 0;
border: 1px solid #ccc;
background-color: white;
z-index: 10; / Ensure it’s above other elements /
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.15); / Optional shadow /
}

.calendar-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.5rem;
border-bottom: 1px solid #eee;
}

.calendar-header button {
background: none;
border: none;
cursor: pointer;
font-size: 1rem;
}

.calendar-weekdays {
display: grid;
grid-template-columns: repeat(7, 1fr);
text-align: center;
padding: 0.2rem 0;
border-bottom: 1px solid #eee;
}

.calendar-days {
display: grid;
grid-template-columns: repeat(7, 1fr);
}

.calendar-day {
width: 2rem;
height: 2rem;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
border: 1px solid transparent; / For consistent spacing /
}

.calendar-day:hover {
background-color: #f0f0f0;
}

.calendar-day.selected {
background-color: #007bff;
color: white;
border-radius: 50%; / Make it a circle /
}
.calendar-day.today {
border: 1px solid #007bff;
border-radius: 50%; / Make it a circle /
}

.calendar-day.empty {
cursor: default; / No pointer on empty cells /
}
input[readonly] {
cursor: pointer;
}
“`

Key CSS Points:

  • position: relative on .datepicker and position: absolute on .calendar: This allows the calendar to be positioned relative to the input field.
  • z-index: 10: Ensures the calendar appears above other elements on the page.
  • Grid Layout: Uses CSS Grid for the weekdays and days for easy alignment.
  • Styling Classes: .selected and .today provide visual cues for the selected date and today’s date.

6. Accessibility Considerations:

  • Keyboard Navigation: We haven’t implemented full keyboard navigation (arrow keys to move between days, Enter to select), but it’s essential for accessibility. You would need to add event listeners for key presses and update the currentDate and selectedDate accordingly. Focus management is also crucial.
  • ARIA Attributes: Adding ARIA attributes (like aria-label, aria-selected, role="grid", etc.) can greatly improve the experience for screen reader users. This is a more advanced topic, but worth researching.
  • Input Field Readonly: the readonly attribute is set to true only when the calendar is displayed. This is essential, otherwise the datepicker will allow free-form text input even when the calendar popup is present.

7. Improvements and Further Enhancements:

  • Year Selection: Add a dropdown or buttons to select the year.
  • Date Range Selection: Modify the component to allow selecting a start and end date.
  • Localization: Adapt the date format and weekday names to different locales.
  • Customizable Styling: Allow users to pass in props to customize the appearance.
  • Error Handling: Improve input validation and provide user-friendly error messages.
  • Testing: Write unit tests to ensure the component functions correctly.
  • Date Formatting Library: Consider using a library like date-fns or Moment.js (although Moment.js is now considered legacy) for more robust date manipulation and formatting. These libraries handle timezones, locales, and complex date calculations much more effectively than native JavaScript Date objects.

This comprehensive guide provides a solid foundation for building a custom datepicker in React. By understanding the core concepts of state management, controlled components, and date manipulation, you can create a flexible and reusable component for your applications. Remember to prioritize accessibility and consider using a date library for more advanced features.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top