Create Calendar Matrix for Datepicker Backbone with Javascript, React, React Hooks, and date-fns library
When building webapp, I often need a datepicker functionality. And I find myself too often reaching for library like jQuery UI. But It’s hard to tweak those library to suits your use case. Sometimes you just need to add a really specific functionality to your datepicker. Also, even if it’s just to tweak the styles and interface of the datepicker, I ended up with overriding a lots of CSS and forced to use !important
to make it works. That’s spooky!
Wouldn’t it be super convenient if there’s JUST a backbone for a datepicker component that you can use to create your own datepicker component, using your own CSS or design sytem, and add functionality as you need it?
Well, currenly I need one and I’ve never learned to make one myself. So for the sake of learning, let’s give it a try then.
Updates
- If you’re only interested in the final result, you can check the repository here https://github.com/vasilenka/use-calendar-matrix.
- I’ve transformed into a React Hook and published it as an NPM package, you can install the package by doing
$_ npm install use-calendar-matrix# or if you prefer yarnyarn add use-calendar-matrix
The Goals
What we want to achieve is something like, given the month and year, we want to generate the matrix of days in that specific month and year. And later we can use that matrix to create a datepicker component, showing a calendar, etc.
We’ll try to achieve that with the help of date-fns. Some kind of lodash for manipulating JavaScript dates in a browser & Node.js.
The Code
JS import {addDays,startOfWeek,differenceInCalendarWeeks,endOfMonth,startOfMonth,format} from "date-fns";function generateCalendarMatrix(year, month, weekStartsOn = 1) {// 1. Generate the date from params, then get the firstDay and lastDay in the monthlet date = new Date(year, month);let firstDay = startOfMonth(date);let lastDay = endOfMonth(date);// 2. Get the start date for our matrixconst startDate = startOfWeek(date, { weekStartsOn });// 3. Get the differences in weeks from lastDay to firstDay// Add (+1) to get total row we need for the matrix to cover all the days in the month// It'll be used as total rows needed for our matrixconst matrixRows =differenceInCalendarWeeks(lastDay, firstDay, { weekStartsOn }) + 1;// 4. Set the number of days in a week.// It'll be used as total columns needed for our matrixconst matrixColumns = 7;// 5. Get the total days that we are going to generate.const totalDays = matrixRows * matrixColumns;// Preparations complete! Let's generate the calendar matrixlet calendar =// 6. Generate an empty Array from the totalDaysArray.from({ length: totalDays });// 7. Assign a Date value to each value of the array// We'll get an array with each value is a Date value.map((_, index) => addDays(startDate, index))// 8. use Array.reduce to transform our array for each week// We want to cut the array at the beginning of each week.reduce((matrix, current, index, days) =>index % matrixColumns === 0? [...matrix, days.slice(index, index + matrixColumns)]: matrix,[]);return calendar};console.log(generateCalendarMatrix(2019, 8))
Note
- Please note that Javascript
month
start from 0 to 11, so for example if you want to generate for September, you’ll assign 8 as the month value. - Also date-fns
weekStartsOn
start from 0 to 6, with 0 for sunday and 6 is saturday.
Make it into a React Hook
To make it even better, we can transform the code into a React Hook. So you can just supply the year
and month
value, and it will return a calendar matrix that will automatically updated when the year
and month
value changed.
Here’s the code for the React Hook
JS import { useEffect, useState } from "react"import {addDays,startOfWeek,differenceInCalendarWeeks,endOfMonth,startOfMonth,} from "date-fns"function generateCalendarMatrix(year, month, weekStartsOn = 1) {let date = new Date(year, month);let firstDay = startOfMonth(date);let lastDay = endOfMonth(date);const startDate = startOfWeek(date, { weekStartsOn });const matrixRows =differenceInCalendarWeeks(lastDay, firstDay, { weekStartsOn }) + 1;const matrixColumns = 7;const totalDays = matrixRows * matrixColumns;let calendar =Array.from({ length: totalDays });.map((_, index) => addDays(startDate, index)).reduce((matrix, current, index, days) =>index % matrixColumns === 0? [...matrix, days.slice(index, index + matrixColumns)]: matrix,[]);return calendar};export const useCalendarMatrix = (year, month, weekStartsOn = 1) => {let [matrix, setMatrix] = useState(generateCalendarMatrix(year, month, weekStartsOn))useEffect(() => {setMatrix(generateCalendarMatrix(year, month, weekStartsOn))}, [year, month])return [ matrix ]}
and then used it your component like this:
JS import React from "react"import useCalendarMatrix from "use-calendar-matrix"const MyCalendar = () => {let [matrix] = useCalendarMatrix(2019, 8)return (<ul>{matrix.map((week, index) => (<ul>{week.map((day, i) => (<li>{day}</li>))}</ul>))}</ul>)}
In the component above, we’re trying to generate a calendar matrix for September 2019
and after applying some styling to the value, you’ll get yourself a calendar matrix like this:
September 2019
- 26
- 27
- 28
- 29
- 30
- 31
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 1
- 2
- 3
- 4
- 5
- 6
With the calendar matrix, you can do whatever you want and add any functionality you need for your use case. For example, add a next and prev month button, or return a value whenever the day in calendar matrix receive a click event.
Good luck!