If you have gone through other tutorials for building a react calendar and struggled to follow along, you’re going to succeed here. Congratulations on getting to the right resource.
The beautiful calendar that you’ll create in next few minutes will look like the one embedded below. Of course, with your modifications.
Breakdown of the calendar tutorial
Use codesandbox for the quick environment, no setup required on your machine.
Here’s the directory structure of this project.
The code for Calendar component:
import React from "react";export default class Calendar extends React.Component {
render() {
return (
<div>
<h2>Calendar</h2>
</div>
);
}
}
Include Calendar component to index.js
import React from “react”;
import ReactDOM from “react-dom”;import Calendar from “./components/calendar”;
function App() {
return (
<div className=“App”>
<Calendar />
</div>
);
}const rootElement = document.getElementById(“root”);
ReactDOM.render(<App />, rootElement);
It must show Calendar heading if you included the component properly in the react calendar. Verify the output before continuing from here.
To display days, install moment
.
Add moment
to dependencies
Importmoment
to calendar component.
import moment from ‘moment’
Get short week day withmoment.weekdaysShort();
.
weekdayshort = moment.weekdaysShort();
Map the day to a function that returns short day wrapped in <th>
tag. The tag will contain a date short name.
let weekdayshortname = this.weekdayshort.map(day => {
return (
<th key={day} className=“week-day”>
{day}
</th>
);
});
Grab the beautiful calendar CSS from tail.Datetime
. tail.Datetime
can be used as an npm package or can be embedded using a CDN link (jsDelivr).
Finally, display in a table.
The month starts with 1, but which day does the first of this month fall on?
While creating a table style calendar, we need to know where to start with the number 1.
Is it a Monday or a Wednesday or a Friday?
Create a moment state variable.
state = {
dateObject: moment()
}
We need to store moment object to as the first day of the month. The state will be modified using an external function.
Create a getter function to retrieve the first weekday in a month.
firstDayOfMonth = () => {
let dateObject = this.state.dateObject;
let firstDay = moment(dateObject)
.startOf(“month”)
.format(“d”);
return firstDay;
};
First, we get current moment object from state and store as a dateObject variable.
Then, we use moment function to get the first weekday of a month. The function returns this day.
Create a blank cell and start filling with the first date of the month in a render function
let blanks = [];
for (let i = 0; i < this.firstDayOfMonth(); i++) {
blanks.push(
<td className=“calendar-day empty”>{“”}</td>
);
}
It generates an empty </td>
with class empty if a counter is less than firstDayOfMonth.
Next, generate </td>
of date in the month.
let daysInMonth = [];
for (let d = 1; d <= this.daysInMonth(); d++) {
daysInMonth.push(
<td key={d} className=“calendar-day”>
{d}
</td>
);
}
Now, there’s <td>
with or without epmty class.
Define some more variables
</td>
while going to a new row</td>
to assign to each rowvar totalSlots = […blanks, …daysInMonth];
let rows = [];
let cells = [];
Loop through totalSlots to get a calendar structure of a week.
totalSlots.forEach((row, i) => {
if (i % 7 !== 0) {
cells.push(row); // if index not equal 7 that means not go to next week
} else {
rows.push(cells); // when reach next week we contain all td in last week to rows
cells = []; // empty container
cells.push(row); // in current loop we still push current row to new container
}
if (i === totalSlots.length - 1) { // when end loop we add remain date
rows.push(cells);
}
});
Wrap all rows in a </td>
let daysinmonth = rows.map((d, i) => {
return <tr>{d}</tr>;
});
and</td>
in <tbody>
<table className=“calendar-day”>
<thead>
<tr>{weekdayshortname}</tr>
</thead>
<tbody>{daysinmonth}</tbody>
</table>
The result shows a valid month, valid as in first of the month falls on a correct day.
Some a little detail, such as highlighting the current day, can make things convenient.
Find the current day.
currentDay = () => {
return this.state.dateObject.format(“D”);
};
Add a conditional operator to check if the day is a current day. If it is a current day, add a classtoday
while pushing to the daysInMonth
.
let daysInMonth = [];
for (let d = 1; d <= this.daysInMonth(); d++){
let currentDay = d == this.currentDay() ? “today” : “”;
daysInMonth.push(
<td key={d} className={calendar-day ${currentDay}
}>
{d}
</td>);
}
}
The result highlights the current day.
The month shows, but which month it is? We’re missing the month name.
Show the current month name on top of the calendar.
After calendar table div insert month picker div.
return (
<div className=“tail-datetime-calendar”>
<div className=“calendar-navi”>
</div>
)
Insert the current month name with momentObject
.
Create a getter function to do it.
month = () => {
return this.state.dateObject.format(“MMMM”);
};
Show the month in the div.
return (
<div className=“tail-datetime-calendar”>
<div className=“calendar-navi”>
{this.month()}
</div>
)
You will see the name of a current month in the calendar.
It’s too ugly, so add CSS class from tail.DateTime
.
<span data-tail-navi=“switch” class=“calendar-label”>
{this.month()}
</span>
There’s no month picker, the same month name displays there forever.
Create a function to render the month table
Get all months from an moment
object and assign it to allMonths
state
state = {
dateObject: moment(),
allmonths :moment.months()
};
Create a function to handle this month table
MonthList = props => {}
props are the selected month object (Jan-Dec).
let months = [];
props.data.map(data => {
months.push(
<td>
<span>{data}</span>
</td>
);
});
Create a list of cells that contain a month name. Define rows to store td
while going through rows.
let rows = [];
let cells = [];
For table, grab condition from calendar table
months.forEach((row, i) => {
if (i % 3 !== 0 || i == 0) { // except zero index
cells.push(row);
} else {
rows.push(cells);
cells = [];
cells.push(row);
}
});
rows.push(cells); // add last row
Map over the rows and wrap in<tr>
.
let monthlist = rows.map((d, i) => {
return <tr>{d}</tr>;
});
There’s a table, put it in a . Return a proper table in HTML, styled with some tail.Datetime
css.
return (
<table className=“calendar-month”>
<thead>
<tr>
<th colSpan=“4”>Select a Month</th>
</tr>
</thead>
<tbody>{monthlist}</tbody>
</table>
);
Just render it in the parent.
<div className=“calendar-date”>
<this.MonthList data={moment.months()} />
</div>
The result is a month selector.
When clicked on month selector, the selector bar in month selector should change the month name to the selected month.
Create a setMonth
function.
setMonth = month => {
let monthNo = this.months.indexOf(month);// get month number
let dateObject = Object.assign({}, this.state.dateObject);
dateObject = moment(dateObject).set(“month”, monthNo); // change month value
this.setState({
dateObject: dateObject // add to state
});
};
Add it to <td>
props.data.map(data => {
months.push(
<td
key={data}
className=“calendar-month”
onClick={e => {
this.setMonth(data);
}}
>
<span>{data}</span>
</td>
);
});
Click on any month, and it should appear as in the picture below.
Once a different month is selected, its job is done. It should hide a month table.
To handle displaying month picker, add a showMonthTable
state.
state = {
showMonthTable:false,
dateObject: moment(),
allmonths: moment.months()
};
To hide on first t load, add simple check state in the calendar-date
div.
<div className=“calendar-date”>
{this.state.showMonthTable &&
< this.MonthList data = {moment.months()} />}
</div>
The month table will be hidden.
Create a function showTable
to display month selector table when clicked on month name.
<div className=“calendar-navi”
onClick={e => {
this.showMonth();
}}
>
Define action for toggle the state.
showMonth = (e, month) => {
this.setState({
showMonthTable: !this.state.showMonthTable
});
};
Every click will toggle the state, so it will be used to show and hide.
A calendar is not hiding when month-selector is displayed. Only one should be visible at a time, this is expected behavior in a calendar.
{ !this.state.showMonthTable && (
<div className=“calendar-date”>
<table className=“calendar-day”>
<thead>
<tr>{weekdayshortname}</tr>
</thead>
<tbody>{daysinmonth}</tbody>
</table>
</div>)}
Wrap calendar view with !showMonthTable
, this will show calendar when showMonthTable
is false
.
The calendar shows the same data for any month. Set appropriate dateObject
while changing the month.
setMonth = month => {
let monthNo = this.months.indexOf(month);// get month number
let dateObject = Object.assign({}, this.state.dateObject);
dateObject = moment(dateObject).set(“month”, monthNo); // change month value
this.setState({
dateObject: dateObject // add to state
showMonthTable: !this.state.showMonthTable
});
};
The result displays a different calendar for each month.
Using the same pattern of months, create a year picker and select month.
Show the current year on side of the month name.
Create a getter function to get a year from dateObject
.
year = () => {
return this.state.dateObject.format(“Y”);
};
Display it as
<span className=“calendar-label”>
{this.year()}
</span>
The result displays the year on the right of the month.
After clicking on the year, it should show a year table to select from.
YearTable = props => {
let months = [];
let nextten = moment()
.set(“year”, props)
.add(“year”, 12)
.format(“Y”);
Receive current year and create next 12 to create year range.
To create a year range, create a function named getDates
.
getDates(startDate, stopDate) {
var dateArray = [];
var currentDate = moment(startDate);
var stopDate = moment(stopDate);
while (currentDate <= stopDate) {
dateArray.push(moment(currentDate).format(“YYYY”));
currentDate = moment(currentDate).add(1, “year”);
}
return dateArray;
}
Send start and end range.
let twelveyears = this.getDates(props, nextten);
Get the next twelve years’ object and use it to create year table.
twelveyears.map(data => {
months.push(
<td
key={data}
className=“calendar-month”
onClick={e => {
this.setYear(data);
}}
>
<span>{data}</span>
</td>
);
});
let rows = [];
let cells = [];months.forEach((row, i) => { if (i % 3 !== 0 || i == 0) { cells.push(row); } else { rows.push(cells); cells = []; cells.push(row); } }); rows.push(cells); let yearlist = rows.map((d, i) => { return <tr>{d}</tr>; }); return ( <table className="calendar-month"> <thead> <tr> <th colSpan="4">Select a Yeah</th> </tr> </thead> <tbody>{yearlist}</tbody> </table> );
};
It’s the same function used in the month table, setMonth
changed to setYear
to trigger the year selected.
Now, display the year in calendar view.
<div className=“calendar-date”>
<this.YearTable props={this.year()} />
{this.state.showMonthTable && (
<this.MonthList data={moment.months()} />
)}
</div>
This results in year selector.
Change state when the selected year changes.
setYear = year => {
// alert(year)
let dateObject = Object.assign({}, this.state.dateObject);
dateObject = moment(dateObject).set(“year”, year);
this.setState({
dateObject: dateObject
});
};
It’s same moment
object in month, parameter being year
this time.
This selects the year.
It works but hides year selector after it’s selected. Use showYearTable
state to display and hide the year table.
state = {
showYearTable: false,
showMonthTable: false,
showDateTable: true,
dateObject: moment(),
allmonths: moment.months(),
};
Apply this state to the view calendar table only, hiding the year table.
<div className=“calendar-date”>
{this.state.showYearTable && (
<this.YearTable props={this.year()} />
)}
{ this.state.showMonthTable && (
<this.MonthList data={moment.months()}
/>
)}</div>
{this.state.showDateTable && (
<div className=“calendar-date”>
<table className=“calendar-day”>
<thead>
<tr>{weekdayshortname}</tr>
</thead>
<tbody>{daysinmonth}</tbody>
</table>
</div>
)}
Year table is now hidden in this react calendar.
While clicking on a year, date table should hide and year table should be shown.
Create a function named showYearTable
and toggle state in year and date view.
showYearTable = (e) => {
this.setState({
showYearTable: !this.state.showYearTable,
showDateTable: !this.state.showDateTable
});
};
Add onClick
event to year label
<span className=“calendar-label” onClick={(e)=>this.showYearTable()} >
This results in proper year selector.
Show month table after selecting a year
Toggle the state from setYear
function.
setYear = year => {
let dateObject = Object.assign({}, this.state.dateObject);
dateObject = moment(dateObject).set(“year”, year);
this.setState({
dateObject: dateObject,
showMonthTable: !this.state.showMonthTable,
showYearTable: !this.state.showYearTable
It results in this react calendar displaying month selector after selecting a year.
After selecting year and month, we need to change date view.
setMonth = month => {
let monthNo = this.state.allmonths.indexOf(month);
let dateObject = Object.assign({}, this.state.dateObject);
dateObject = moment(dateObject).set(“month”, monthNo);
this.setState({
dateObject: dateObject,
showMonthTable: !this.state.showMonthTable,
showDateTable: !this.state.showDateTable
});
};
Toggle the state variable and the result is below.
Add a previous button
<span onClick={e => {
this.onPrev();
}}
class=“calendar-button button-prev”
/>
Also, add the next button
<span onClick={e => {
this.onNext();
}}
className=“calendar-button button-next”
/>
Handle it with an empty function to prevent any error.
onPrev = () => {};
onNext = () => {};
will show the result
The code below handles next and previous month and year
onPrev = () => {
let curr = “”;
if (this.state.showYeahTable == true) {
curr = “year”;
} else {
curr = “month”;
}
this.setState({
dateObject: this.state.dateObject.subtract(1, curr)
});
};
onNext = () => {
let curr = “”;
if (this.state.showYeahTable == true) {
curr = “year”;
} else {
curr = “month”;
}
this.setState({
dateObject: this.state.dateObject.add(1, curr)
});
};
that result
for (let d = 1; d <= this.daysInMonth(); d++) {
let currentDay = d == this.currentDay() ? “today” : “”;
daysInMonth.push(
<td key={d} className={calendar-day ${currentDay}
}>
<span onClick={e => {
this.onDayClick(e, d);
}} >
{d}
</span>
</td>
);
}
Pass the current d
value upon onDayClick
.
onDayClick = (e, d) => {
this.setState({
selectedDay: d
},
() => {
console.log("SELECTED DAY: ", this.state.selectedDay);
}
);
};
The selectedDay
is a state of a selected day on the date picker. Use this state to get the selected date.
React Calendar is done, but it’s not a typical tutorial, so here’s some homework for you.
You have seen the components, there are things to do with it.
Send your pull requests here.
☞ Build a Basic CRUD App with Node and React
☞ React vs Angular: An In-depth Comparison
☞ Build a Simple CRUD App with Python, Flask, and React
☞ Build a Basic CRUD App with Laravel and React
☞ Full Stack Developers: Everything You Need to Know
☞ React Hooks Tutorial for Beginners: Getting Started With React Hooks
☞ Learn React - React Crash Course 2019 - React Tutorial with Examples
☞ The Complete React Web Developer Course (2nd Edition)
☞ Beginner Full Stack Web Development: HTML, CSS, React & Node
☞ React 16 - The Complete Guide (incl. React Router 4 & Redux)
Originally published at https://programmingwithmosh.com
#reactjs #web-development