DEV Community

David
David

Posted on

# Create a simple Date Range Picker in React.

video

Instantiate a new project

$ npm create vite@latest

Configure the app with the following:-

Project name: calendar-app

Framework: React

Variant: JavaScript


$ cd calendar-app

$ npm install

$ npm run dev

# to start the development server 
#open the development server in your browser
# mine is at http://localhost:5173/

Enter fullscreen mode Exit fullscreen mode

Open your newly created app in your preferred editor.
For vs-code users in the project directory run $ code . it will open your application in vs-code.
Remove all unneccasary files from the scaffolded project and your project should look like the screenshot below

#app.js

import './App.css'



function App() {

  return <>welcome</>

}

export default App
Enter fullscreen mode Exit fullscreen mode

first-screen

For our project we will be making use of the react-date-range package

from your app/directory run

npm i react-date-range react-date-range

this will add the package to your app dependencies.

we would also be using the date-fns package so

from your terminal again run

npm install date-fns date-fns

Inside your /src directory create a folder called components, inside the components folder, create an empty file singleCalendar.jsx, you can do all these from the bash terminal.

$ cd src && mkdir components
$ cd components && touch singleCalendar.jsx

Enter fullscreen mode Exit fullscreen mode

open the new file singleCalendar.jsx and import the <DateRange /> component

import { DateRange } from 'react-date-range'

export const SingleCalendar = () => {

  return (

    <div>

      <DateRange />

    </div>

  )

}
Enter fullscreen mode Exit fullscreen mode

then import the SingleCalandar into App.jsx your code should look like this

import './App.css'

import { SingleCalendar } from './components/singleCalendar'

function App() {
  return (
    <div>
      <SingleCalendar />
    </div>
  )
}

export default App

Enter fullscreen mode Exit fullscreen mode

Next, we would import css from 'react-date-range' for styling. Add the following code to your main.jsx file

import 'react-date-range/dist/styles.css'
import 'react-date-range/dist/theme/default.css'
Enter fullscreen mode Exit fullscreen mode

You can remove all the default styling that comes with Vite.

When you are importing styles for a component, you should put the styles in the topmost component, so that you can use the same styles across other places in your app.

Your main.jsx should look like this


import React from 'react'

import ReactDOM from 'react-dom/client'

import App from './App.jsx'

import './index.css'

//Add styles for the calendar app

import 'react-date-range/dist/styles.css' // main style file
import 'react-date-range/dist/theme/default.css'

ReactDOM.createRoot(document.getElementById('root')).render(

  <React.StrictMode>

    <App />

  </React.StrictMode>

)

Enter fullscreen mode Exit fullscreen mode

https://res.cloudinary.com/diggungrj/image/upload/v1683865345/calendar-initial_oyjmxj.png

Good job getting here so far 👏

Let's add a container where our calendar result will be viewed, we would be using an input element to store the calendar value.


import { DateRange } from 'react-date-range'

export const SingleCalendar = () => {

  return (

 <div className='container'>

      <DateRange className='calendarStyle' />

      <div>

        <input type='text' className='single-calendar-input' />

      </div>

    </div>

  )

}

Enter fullscreen mode Exit fullscreen mode

https://res.cloudinary.com/diggungrj/image/upload/v1683865344/calendar-input-initial_fihtyd.png

Then let us style the input container,

  • create a calendar icon
  • style the input container

create a calendaricon file and the add the following jsx to the file.
I copied this code from heroicons

export const CalendarIcon = () => {
  return (
    <svg
      xmlns='http://www.w3.org/2000/svg'
      fill='none'
      viewBox='0 0 24 24'
      strokeWidth={1.5}
      stroke='currentColor'
      className='w-6 h-6'
    >
      <path
        strokeLinecap='round'
        strokeLinejoin='round'
        d='M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 012.25-2.25h13.5A2.25 2.25 0 0121 7.5v11.25m-18 0A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75m-18 0v-7.5A2.25 2.25 0 015.25 9h13.5A2.25 2.25 0 0121 11.25v7.5m-9-6h.008v.008H12v-.008zM12 15h.008v.008H12V15zm0 2.25h.008v.008H12v-.008zM9.75 15h.008v.008H9.75V15zm0 2.25h.008v.008H9.75v-.008zM7.5 15h.008v.008H7.5V15zm0 2.25h.008v.008H7.5v-.008zm6.75-4.5h.008v.008h-.008v-.008zm0 2.25h.008v.008h-.008V15zm0 2.25h.008v.008h-.008v-.008zm2.25-4.5h.008v.008H16.5v-.008zm0 2.25h.008v.008H16.5V15z'
      />
    </svg>
  )
}

Enter fullscreen mode Exit fullscreen mode

next, add the css style

note: I have included some classes in the styles for some elements that have not been created yet.


#  index.css

*,
*::after,
*::before {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

html {
  font-size: 62.5%;
}

.wrapper {
  display: flex;
  justify-content: center;
  align-items: center;
}

.container {
  padding: 5rem;
}

.calendarStyle {
  border: 1px solid #eee;
  font-family: poppins;
  border-radius: 1.5rem;
  z-index: 999;
  position: relative;
  background-color: #fff;
}

.icon {
  height: 1.6rem;
  width: 2rem;
}

input {
  height: 100%;
  width: 90%;
  border: none;
  outline: none;
}

.single-calendar-input {
  height: 4rem;
  width: 35rem;
  border-radius: 0.5rem;
  border: 1px solid #ddd;
  padding: 0 10px;
  font-weight: 500;
  color: #686868;
  margin-bottom: 1rem;
  display: flex;
  align-items: center;
}

.element-container {
  position: relative;
}

.dropdown {
  position: absolute;
  top: 50px;
  left: 0px;
}

.dropdown.active {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
  transition: 500ms ease;
}

.dropdown.inactive {
  opacity: 0;
  visibility: hidden;
  transform: translateY(-20px);
  transition: 500ms ease;
}

Enter fullscreen mode Exit fullscreen mode

And also update the singleCalendar component,

import { DateRange } from 'react-date-range'
import { CalendarIcon } from '../assets/calendaricon'

export const SingleCalendar = () => {
  return (
 <div className='container'>
      <div>
        <div className='single-calendar-input'>
          <input type='text' />
          <div className='icon'>
            <CalendarIcon />
          </div>
        </div>
      </div>
      <DateRange className='calendarStyle' />   
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

Your page should look like this now

Nice job so far 👍
screen2

Let's start working on the functionalities

  • we handle the calendar value with useState
  • we will handle the date format
  • we will set an event listener on the input to open the calendar when clicked
  • we will close the calendar when any part of our page is clicked apart from the calendar component

Handle the calendar state


# singleCalendar.jsx

import { useState } from 'react'

import { DateRange } from 'react-date-range'

import { CalendarIcon } from '../assets/calendaricon'


export const SingleCalendar = () => {

  const [state, setState] = useState([

    {

      startDate: new Date(),

      endDate: new Date(),

      key: 'selection',

    },

  ])


  return (

    <div className='container'>

      <DateRange

        className='calendarStyle'

        onChange={(item) => setState([item.selection])}

        ranges={state}

        showSelectionPreview={false}

        editableDateInputs={false}

        showMonthAndYearPickers={false}

        showDateDisplay={false}

        rangeColors={['#4C988E', '#4C988E', '#000000']}

        direction='horizontal'

      />

      <div className='single-calendar-input'>

        <input type='text' />

        <div className='icon'>

          <CalendarIcon />

        </div>

      </div>

    </div>

  )

}

Enter fullscreen mode Exit fullscreen mode

if you console.log({state}) you will see an array of your current selected date range.

screen-3

Next, we will set an event listener on the input to open the calendar when the input element is clicked

  • hide the calendar component
  • show the calendar component when I click on the input element

Let's go,


# singleCalendar.jsx

import { useState } from 'react'

import { DateRange } from 'react-date-range'

import { CalendarIcon } from '../assets/calendaricon'



export const SingleCalendar = () => {

  const [open, setOpen] = useState(false)

  const [state, setState] = useState([

    {

      startDate: new Date(),

      endDate: new Date(),

      key: 'selection',

    },

  ])



  console.log({ state })



  return (

    <div className='container'>

      <div className='element-container'>

        <div className={`dropdown ${open ? 'active' : 'inactive'}`}>

          <DateRange

            className='calendarStyle'

            onChange={(item) => setState([item.selection])}

            ranges={state}

            showSelectionPreview={false}

            editableDateInputs={false}

            showMonthAndYearPickers={false}

            showDateDisplay={false}

            rangeColors={['#4C988E', '#4C988E', '#000000']}

            direction='horizontal'

          />

        </div>

        <div className='single-calendar-input'>

          <input type='text' readOnly onClick={() => setOpen(!open)} />

          <div className='icon'>

            <CalendarIcon />

          </div>

        </div>

      </div>

    </div>

  )

}

Enter fullscreen mode Exit fullscreen mode

Your app should now be hidden and will toggle the visible/hidden state when the input element is clicked.

Show the value of the DateRange state in the input element


import { useState } from 'react'

import { DateRange } from 'react-date-range'

export const SingleCalendar = () => {

  const [open, setOpen] = useState(false)

  const [state, setState] = useState([

    {

      startDate: new Date(),

      endDate: new Date(),

      key: 'selection',

    },

  ])

  return (

    <div className='container'>

      <div className='element-container'>

        <input

          type='text'

          className='single-calendar-input'

          onClick={() => {

            setOpen(!open)

          }}

++          readOnly

++          value={`${state[0].startDate} - ${state[0].endDate}`}

        />

        <div className={`dropdown ${open ? 'active' : 'inactive'}`}>

          <DateRange

            className='calendarStyle'

            onChange={(item) => setState([item.selection])}

            ranges={state}

            showSelectionPreview={false}

            editableDateInputs={false}

            showMonthAndYearPickers={false}

            showDateDisplay={false}

            rangeColors={['#4C988E', '#4C988E', '#000000']}

            direction='horizontal'

          />

        </div>

      </div>

    </div>

  )

}

Enter fullscreen mode Exit fullscreen mode

You will see the unformatted date value inside the input element

https://res.cloudinary.com/diggungrj/image/upload/v1683865344/calendar-5_h83fhc.png

Format the date inside the input element


import { useState, useEffect } from 'react'

import { DateRange } from 'react-date-range'

import { CalendarIcon } from '../assets/calendaricon'

import { format } from 'date-fns'



export const SingleCalendar = () => {

  const [open, setOpen] = useState(false)

  const [state, setState] = useState([

    {

      startDate: new Date(),

      endDate: new Date(),

      key: 'selection',

    },

  ])


  const [formatDate, setFormatDate] = useState({

    startDate: new Date(),



    endDate: new Date(),

  })


  useEffect(() => {

    setFormatDate((prev) => {

      return {

        ...prev,



        startDate: format(state[0].startDate, 'dd/MM/yyyy'),



        endDate: format(state[0].endDate, 'dd/MM/yyyy'),

      }

    })

  }, [state])


  // console.log({ state })

  return (

    <div className='container'>

      <div className='element-container'>

        <div className={`dropdown ${open ? 'active' : 'inactive'}`}>

          <DateRange

            className='calendarStyle'

            onChange={(item) => setState([item.selection])}

            ranges={state}

            showSelectionPreview={false}

            editableDateInputs={false}

            showMonthAndYearPickers={false}

            showDateDisplay={false}

            rangeColors={['#4C988E', '#4C988E', '#000000']}

            direction='horizontal'

          />

        </div>

        <div className='single-calendar-input'>

          <input

            type='text'

            readOnly

            onClick={() => setOpen(!open)}

            value={`${formatDate.startDate}-${formatDate.endDate}`}

          />

          <div className='icon'>

            <CalendarIcon />

          </div>

        </div>

      </div>

    </div>
  )
}

Enter fullscreen mode Exit fullscreen mode

you may be wondering why I did not use the format method directly inside my component, the reason is because of an error caused by using format method inside the input element to directly format the value but it will throw an error .

https://res.cloudinary.com/diggungrj/image/upload/v1683865344/calendar-6_lcuyis.png

Finally, handle click outside so that when I click outside the component or the body of the page the calendar will close


# singleCalendar.jsx

import { useState, useEffect, useRef } from 'react'

import { DateRange } from 'react-date-range'

import { CalendarIcon } from '../assets/calendaricon'

import { format } from 'date-fns'



export const SingleCalendar = () => {

  const [open, setOpen] = useState(false)

  const [state, setState] = useState([

    {

      startDate: new Date(),

      endDate: new Date(),

      key: 'selection',

    },

  ])


  const [formatDate, setFormatDate] = useState({

    startDate: new Date(),

    endDate: new Date(),

  })

  useEffect(() => {

    setFormatDate((prev) => {

      return {

        ...prev,



        startDate: format(state[0].startDate, 'dd/MM/yyyy'),



        endDate: format(state[0].endDate, 'dd/MM/yyyy'),

      }

    })

  }, [state])



  // console.log({ state })



  // handle click outside

  const calendarRef = useRef()



  useEffect(() => {

    const handler = (e) => {

      if (!calendarRef?.current.contains(e.target)) {

        setOpen(false)

      }

    }



    document.addEventListener('mousedown', handler)



    return () => {

      document.removeEventListener('mousedown', handler)

    }

  })



  return (

    <div className='container'>

      <div className='element-container' ref={calendarRef}>

        <div className={`dropdown ${open ? 'active' : 'inactive'}`}>

          <DateRange

            className='calendarStyle'

            onChange={(item) => setState([item.selection])}

            ranges={state}

            showSelectionPreview={false}

            editableDateInputs={false}

            showMonthAndYearPickers={false}

            showDateDisplay={false}

            rangeColors={['#4C988E', '#4C988E', '#000000']}

            direction='horizontal'

          />

        </div>



        <div className='single-calendar-input'>

          <input

            type='text'

            readOnly

            onClick={() => setOpen(!open)}

            value={`${formatDate.startDate}-${formatDate.endDate}`}

          />

          <div className='icon'>

            <CalendarIcon />

          </div>

        </div>

      </div>

    </div>

  )

}

Enter fullscreen mode Exit fullscreen mode

The complete source code can be found here

Top comments (0)