How to Create Custom React Calendar Date Range Picker
Tags: react, typescript, calendar, tutorial
Published: 2022-08-07 06:20:00


A few weeks into my first ever programming job, I was assigned the task of creating a form.  The purpose of the form was to gather a few pieces of information from the user so they can perform a search.  The user needs to choose a location from a dropdown and pick the start and end date along with times.  

Being new to React and javascript in general, I just started out with a very basic form.  

```
<form>
  <input type="text" 
   name="location"
   />
  <input type="date" 
   name="start"
   />
<input type="date" 
   name="end"
   />
<button type="submit">Search</button>
</form> 
```
 I began hitting hurdles which I believe are common to many React developers.  How do I customize and style the form?  I have a design mocked up that I have to follow and that requires specific colors, shapes, fonts,  etc. and native html forms just don't give the developer much control.  

I spent time researching and found all kinds of tutorials and  third party libraries to try.  I tried free ones, free trials of paid ones, open source ones.  They each have their own learning curve just to be able to use them.  It was time-consuming researching and experimenting, but it was a valuable experience just learning how to work with and implement third-party components.  At the end of the day, however, I was just not able to overcome the barriers to customize the form and inputs to exactly how I needed them, specifically the date pickers.  

I showed my team a "pretty good" version using [react-hook-forms](https://react-hook-form.com/) and [react-datepicker](https://www.npmjs.com/package/react-datepicker), but when they asked me to move and resize and reshape and change colors, it was like I had to hack and custom build and add !important everywhere to override built in CSS, so much that is was decided it would be more efficient to build it from scratch.  

Although the finished form had some really cool time pickers and sliders and custom drop downs with autofill, the focus of this tutorial will be on the calendar/date-range-picker portion.

To extract just the most important parts, and to keep it simple, I am starting out with `npx create-react-app my-app --template typescript` and removing some unnecessary files and logos.  

If you want to TLDR and skip straight to the finished code, feel free to do so [here](https://github.com/Brian-1150/MyDatePickerDemo).  Or if you want to implement my published version, which has more features than this demo, it can be found on [npm](https://www.npmjs.com/package/date-range-calendar) or simply `npm i date-range-calendar`.

I am starting out with a similar bare bones form to the one above, just to lay a foundation and work up from there in small incremental steps. 
Scaffold out a form with a few inputs and a submit button in **App.tsx**.  Make a few divs so we can separate the form from the rest of the page.  Something like this should suffice
```
 import React from 'react';
import './App.css';

const [form, formSet] = useState({
    destination: '',
    start: '',
    end: ''
  });

  function handleSubmit() {
    console.log(form);
  }

 function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
    formSet({ ...form, [e.target.name]: e.target.value });
  }

function App() {
  return (
    <div className='container'>
      <form className='form' >
        <div className='input'>
          <input type="text"
            name="location"
          />
        </div>
        <div className='input'>
          <input type="date"
            name="start"
          />
        </div>
        <div className='input'>
          <input type="date"
            name="end"
          />
        </div>
        <div className='input'>
          <button onClick={handleSubmit} type='button'>Search</button>
        </div>
      </form>

    </div >
  )
}

export default App;

```
 Add some style to **App.css** and put some borders around our divs to help visualize the placement of the form on our page.  
```
.App {
  text-align: center;
}

.container {
  border: 5px solid rgb(46, 57, 110);
  margin: 25px auto;
  height: 600px;
  width: 500px;
}

.form {
  border: 2px solid blue;
  height: 300px;
  width: 300px;
  margin: 25px auto;
  padding: 5px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}
```
We now have a basic form with three inputs, some state values, an onChange handler, and a submit handler.  So the native input for type date is your basic default datePicker.  It is perfectly functional, but we need something more modern and with more style. 

Let's remove the two date inputs and replace them with a component.  Create a new file called **DatePicker.tsx**.

And build a basic react component.  This component will take some props so it can set the values in the parent.  Let's start out with some hard coded numbers so we can get an idea of what this might look like:
```
import React from "react"
import './DatePicker.css';

type DatePickerProps = {

}

const DatePicker = (props: DatePickerProps) => {

    return (
        <>
            <div className='content'>
                <div className="calendar">
                    <h4>June 2022</h4>
                    <div className="row"> 1  2  3  4  5  6  7</div>
                    <div className="row"> 8  9 10 11 12 13 14</div>
                    <div className="row">15 16 17 18 19 20 21</div>
                    <div className="row">22 23 24 25 26 27 28</div>
                    <div className="row">29 30 31</div>
                </div>
            </div>
        </>
    )
};

export default DatePicker;

```
And here is some style for **DatePicker.css**
```
.content {
    position: absolute;
    top: 65%;
    bottom: 10%;
    left: 50%;
    transform: translate(-50%, 0);
    background: #fff;
    overflow: auto;
    border: 1px solid #ccc;
    border-radius: 11px;
}

.calendar{
    width: 90%;
    display: flex;
    flex-flow: column nowrap;
    align-items: center;
}
.row{
    display: flex;
    flex-flow: row nowrap;
    justify-content: space-between;
    align-items: center;
}
```
Now back in **App.tsx**, we want our calendar to pop up whenever the user clicks 'start' or 'end', so add another state value 
`const [open, openSet] = useState(false);` 
And conditionally render that child component dependent on the _open_ value.  
```
 <div onClick={() => openSet(!open)}>{form.start}</div>
 <div onClick={() => openSet(!open)}>{form.end}</div>
  {open && <DatePicker />}
```
Now the calendar opens and closes by clicking either start or end.  We are building a dateRangePicker so we want the calendar to open and let the user choose both dates.  
Next step is to build a real calendar and populate it with real values accounting for leap year etc.  Make a new file called **CalendarUtils.ts** and we will keep all our helper methods here.  We will have an array of the month names:
`const months = [" ", "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]`
The calendar will have months.  Each month will have either 4, 5, or 6 rows.  And each row will have 7 individual blocks or boxes.  Most of them will have days, but a few at the beginning and end will be blank.  Let's start at the box level and work our way up.  A box will simply be a styled <p> element with a value.  
```
type BoxProps = {
    value: number | string
   }

 function Box(props: BoxProps) {
   
    return (
        <p>
            {props.value}
        </p>
    );
}
```
Next, let's render some Boxes in another component call Row.  Each row will have 7 boxes.  So let's create a number[] with a for loop and slice it and map over it to create a row of 7 boxes.  We need to specify what number each row will start and end with so we'll pass those values as props. 

```
type IRowProps = {
startIndex: number
endIndex: number
}

function CalendarRow(props: IRowProps) {
    const dates: number[] = [];
    for (let i = 1; i < 32; i++) {
        dates.push(i);
    }
    return (
        <>
            <div >
                {
                  dates.slice(props.startIndex, props.endIndex).map((d, index) =>
                        <Box value={d} />
                    )
                }
            </div>
        </>
    )
}
```
As you can see, we create an interface or prop type for every component by default so we can easily pass props as we discover what needs to be passed.  
Now we need a Month component to render these rows.  Each row needs the indexes, which we will hard code for now, as well as the month and year.  
```
type IMonthProps = {
   
}

function Month(props: IMonthProps) {
 
    return (
        <>
            <h4 className='month'>February 2026</h4>
            <div className='days'>Sun Mon Tue Wed Thu Fri Sat</div>
            <div className='calendar'>
                <Row startIndex={0} endIndex={6}/>
                <Row startIndex={7} endIndex={13}/>
                <Row startIndex={14} endIndex={19}/>
                <Row startIndex={21} endIndex={25}/>
            </div>
        </>
    )
}
```
And update some style in **DatePicker.css**
```
.calendar {
    display: flex;
    flex-flow: column nowrap;
    align-items: center;
}
.month{
    margin:6px;
}
.row {
    display: flex;
    flex-flow: row nowrap;
    justify-content: space-between;
    align-items: center;
}
```
Now we can go back to the main export function for the DatePicker and render this <Month/> component.  But then let's go ahead and 
use a for loop to get 2 years of months to render.
```
const DatePicker = (props: DatePickerProps) => {
    let monthsArray = new Array();
       for (let i = 0; i < 24; i++) {
       monthsArray.push(<Month/>)
       }

    return (
        <>
            <div className='content'>
                <div className="calendar">
                   {monthsArray}
                </div>
            </div>
        </>
    )
};
```
Next step is to start replacing some of our hard coded days and months with actual date values by using Javascript Date() object.  We could use a library, [Day.js](https://day.js.org/) would be my choice, but we can keep our dependencies to a minimum for now.  

This is where the real fun begins.  Let's update the Month component to take in a couple props so we can determine the actual start and end indexes for each row.  Instead of passing the start and end index to Row.tsx, I want to handle that logic in the component itself, but I do want to specify which row number.  Remember, we always need at least 4 rows, usually 5, and occasionally 6.  Also we will need to write some helper methods as we go.  We need a method that gives us an array the exact values of the given month and empty strings for the few days before an after.  So we will need to pass to that method the number of days in the month and numerical day of the week that month starts on.  We have to get creative with how to make these methods give us the right days and the accurate number of days in each month.  Here is what I came up with.  There are certainly other ways of accomplishing this, but this works and it is efficient.    
```
type IMonthProps = {
month: number
year: number
}
```
Then in our DatePicker, we will add some logic to determine the dates and pass those down to the Month component.  
```
    const today = new Date()
    let month = today.getMonth() + 1
    let year = today.getFullYear()
    const numOfDays = new Date(today.getFullYear(), today.getMonth() + 1, 0).getDate();
    const monthName = firstDateOfMonth.toLocaleString('default', { month: 'long' })

    for (let i = 0; i < 24; i++) {
        monthsArray.push(<Month month={month} year={year} />)
        year = month == 12 ? year + 1 : year
        month++
    }
```
**Helpers**
```
export const getNumberOfRows = (numberOfDaysInMonth: number, dayOfTheWeek: number) => {
    switch (numberOfDaysInMonth - (21 + (7 - dayOfTheWeek))) {
        case 0: return 4;
        case 8: case 9: return 6;
        default: return 5;
    }
}
```
Now we should refactor our **Row.tsx** to build a real dates[] with the right values.  
```
tye IRowProps = {
    dayOfWeek: number
    numberOfDaysInMonth: number
    rowNumber: number
}
function Row(props: IRowProps) {
    const dates = getOneMonthArray(props.dayOfWeek, props.numberOfDaysInMonth)
    let rowNumber = 0;
    rowNumber += props.rowNumber;
    let startIndex: number = (rowNumber * 7);
    let endIndex = startIndex + 7;
  
    return (...)
}
```
We now have a perfectly functioning calendar that defaults to the current month.  You can easily customize the code to suit your use case.  For example, the for loop can be updated to only loop once and therefore, only show the current month.  Or instead of starting with _today_, you can start with a month from the past or future.  
Ok.  Great!  We have a foundation to build on.  The next phase will be making this calendar into an interactive datePicker or dateRangePicker.  We will add onClick handlers, manage the state, highlight the chosen dates, etc.  Remember, in our main App.tsx component is where we our keeping our state, with the main form.  So we'll need to pass down setter functions to our DatePicker.tsx component.  Since we broke this into so many components we either need to prop drill our setters and a couple values all the way down, or use context or a store like redux.  For this demo, we will just prop drill it down.  In **App.tsx**, write two more functions.  One we will call _handleClick()_ and this we will pass all the way down to our **Box.tsx** component and assign it to the <p> in the return.    
What this function needs to do is, capture and handle the date that was clicked, including month, day, and year.  Since we are making a DateRangePicker, not just a DatePicker, we need to know if a date was already chosen.  Furthermore, we need to determine, if one or both dates have already been chosen and if the date clicked is less than or greater than.  I like to start with the onClick in the <p> tag of **Box.tsx** to see what kind of info I have access to and what the type is.  If you add `onClick={(e)=> console.log(e)}` you will see everything you have access to everything you need.  Add the following functions to **App.tsx**
```
const [startDate, startDateSet] = useState<Date | undefined>(undefined);
  const [endDate, endDateSet] = useState<Date | undefined>(undefined);


  function handleCalenderClicks(e: React.MouseEvent<HTMLDivElement>, value: string) {
    let p = e.target as HTMLDivElement
    if (!(startDate && !endDate)) {
      startDateSet(new Date(value))
      formSet({ ...form, start: value as string, end: 'end' })
      endDateSet(undefined)
      resetDivs()
      p.style.color = 'green'
      p.style.backgroundColor = 'lightblue'
    }
    else if (new Date(value) >= startDate) {
      endDateSet(new Date(value))
      formSet({ ...form, end: value as string })
      p.style.color = 'red'
      p.style.backgroundColor = 'lightblue'
    }
    else {
      startDateSet(new Date(value))
      formSet({ ...form, start: value as string })
      resetDivs()
      p.style.color = 'green'
      p.style.backgroundColor = 'lightblue'
    }
  }
  
  function resetDivs() {
    let container = document.querySelectorAll('p')
    container.forEach((div) => {
      let box = div as HTMLParagraphElement;
      if ((box.style.color == 'red' || 'green')) {
        box.style.color = 'inherit';
        box.style.fontWeight = 'inherit';
        box.style.backgroundColor = 'inherit';
      }
    })
  }

```

As you can see here, we are accounting for all possible states our choices can be in and highlighting them and adding green color to our start and red to our end.  The most common condition is to assign to the begin the date that was just clicked and reset everything else.  I have chosen to use the `document.querySelectorAll('p')` in order to unhighlight previous choices, but be careful if you have other interactive <p> tags on the same page.  If you have other <p> tags, but you don't manipulate their styles at all, then the resetDivs() function will not hurt them.  

Be sure and add the function and month and year values to the prop types for each component as needed like:
```
    handleClick: (e: React.MouseEvent<HTMLDivElement>, value: string) => void
month: number
year: number
```
and add them to the components as needed, for example:
```
                <Row month={props.month} year={props.year} handleClick={props.handleClick} dayOfWeek={dayOfWeek} numberOfDaysInMonth={numOfDays} rowNumber={0} />

```
Thanks for reading and please let me know if you have any questions or comments.
Again, the links to the completed code can be found [here](https://github.com/Brian-1150/MyDatePickerDemo).  Or if you want to implement my published version, which has more features than this demo, it can be found on [npm](https://www.npmjs.com/package/date-range-calendar) or simply 'npm i date-range-calendar'.