DEV Community

Cover image for Creating a custom calendar in React from scratch
ibrahim
ibrahim

Posted on

Creating a custom calendar in React from scratch

Usually as developer we don't reinvent the wheel. Most of the time we just end up using a 3rd party library for calendar. But for some cases the 3rd party calendar won't suit our design and functionality. So, when there is no choice, we need to come up with our own calendar.

Let's start, below are the packages we are going to use

I am not going to explain the above library in detail, please refer to the link if you are not familiar with the library above.

Setup your React project by running the command below

  • npx create-react-app custom-calendar && cd custom-calendar
  • npm install dates-generator --save
  • npm install styled-components --save

styled-components is used to apply the css styling to the components and I find it easier to write the css styling using styled-components

Now let's edit the /src/App.js

import React, { useState, useEffect } from 'react';
import styled from 'styled-components'

const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];

const Container = styled.div`
  width: 300px;
  border: 1px solid black;
  margin: 0 auto;
  box-shadow: 10px 10px 0px black;
`

const MonthText = styled.div`
  font-size: 26px;
  font-weight: bold;
  text-align: center;
`

const App = () => {
  const [selectedDate, setSelectedDate] = useState(new Date());
  const [dates, setDates] = useState([]);
  const [calendar, setCalendar] = useState({
    month: selectedDay.getMonth(),
    year: selectedDay.getFullYear(),
  });


  useEffect(() => {}, [])

  return (
    <div style={{ width: '100%', paddingTop: 50 }}>
      <Container>
        <MonthText>
          {months[calendar.month]}
        </MonthText>
      </Container>
    </div>
  );
}

export default App;

Enter fullscreen mode Exit fullscreen mode

If you run this, you will see the current month being rendered in your browser.

selectedDate is the date that we selected in the calendar. By default the date is present date.

dates is the state that will hold all the dates for the given month.

calendar is the state that will hold the month and year for the calendar.

Now lets fill up the calendar with the dates, by default the calendar will be the present month calendar.

...

const [calendar, setCalendar] = useState({
    month: selectedDay.getMonth(),
    year: selectedDay.getFullYear(),
});

useEffect(() => {
  const body = {
    month: calendar.month,
    year: calendar.year
  };
  const { dates, nextMonth, nextYear, previousMonth, previousYear } = datesGenerator(body);

  setDates([ ...dates ]);
  setCalendar({
    ...calendar,
    nextMonth,
    nextYear,
    previousMonth,
    previousYear
  });
}, [])

...

Enter fullscreen mode Exit fullscreen mode

As you can see that, we have added useEffect inside our component. Inside the useEffect we run the datesGenerator function that is provided by dates-generator.

This function will return the all the dates that is available for the given month. We provided the month and year by passing the body inside the datesGenerator function. We can use the previousMonth/nextMonth and previousYear/nextYear attributes to get the previous/next month calendar dates.

Read more on how dates-generator work here

Since now we already store the all the dates in the state, let's display it at our browser.

const days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']

...

return (
  <div style={{ width: '100%', paddingTop: 50 }}>
    <Container>
      <MonthText>
        {months[calendar.month]}
      </MonthText>
      <div>

        <div>
          <table style={{ width: '100%' }}>
            <tbody>
              <tr>
                {days.map((day) => (
                  <td key={day} style={{ padding: '5px 0' }}>
                    <div style={{ textAlign: 'center', padding: '5px 0' }}>
                      {day}
                    </div>
                   </td>
                 ))}
              </tr>

              {dates.length > 0 && dates.map((week) => (
                <tr key={JSON.stringify(week[0])}>
                  {week.map((each) => (
                    <td key={JSON.stringify(each)} style={{ padding: '5px 0' }}>
                      <div style={{ textAlign: 'center', padding: '5px 0' }}>
                        {each.date}
                      </div>
                    </td>
                  ))}
                </tr>
              ))}
            </tbody>
          </table>
        </div>

      </div>
    </Container>
  </div>
);

...
Enter fullscreen mode Exit fullscreen mode

I have add the days variable to be rendered on top of the dates. If you look at your browser now, you will see the calendar for the present month.

Now let's write 3 more function:

  • onClickNext - to go to next month calendar
  • onClickPrevious - to go to previous month calendar
  • onSelectDate - to set the selected date by the user
  const onClickNext = () => {
    const body = { month: calendar.nextMonth, year: calendar.nextYear };
    const { dates, nextMonth, nextYear, previousMonth, previousYear } = datesGenerator(body);

    setDates([ ...dates ]);
    setCalendar({
      ...calendar,
      month: calendar.nextMonth,
      year: calendar.nextYear,
      nextMonth,
      nextYear,
      previousMonth,
      previousYear
    });
  }

  const onClickPrevious = () => {
    const body = { month: calendar.previousMonth, year: calendar.previousYear };
    const { dates, nextMonth, nextYear, previousMonth, previousYear } = datesGenerator(body);

    setDates([ ...dates ]);
    setCalendar({
      ...calendar,
      month: calendar.previousMonth,
      year: calendar.previousYear,
      nextMonth,
      nextYear,
      previousMonth,
      previousYear
    });
  }

  const onSelectDate = (date) => {
    setSelectedDate(new Date(date.year, date.month, date.date))
  }

...
return (
  ...
    <div style={{ padding: 10 }}>
      <div onClick={onClickPrevious} style={{ float: 'left', width: '50%' }}>
        Previous
      </div>
      <div onClick={onClickNext} style={{ float: 'left', width: '50%', textAlign: 'right' }}>
        Next
      </div>
    </div>
    <MonthText>
      {months[calendar.month]}
    </MonthText>

    ...    
      <div onClick={() => onSelectDate(each.jsDate)} style={{ textAlign: 'center', padding: '5px 0' }}>
        {each.date}
      </div>
    ...

    <div style={{ padding: 10 }}>
      Selected Date: {selectedDate.toDateString()}
    </div>
  </Container>
  ...
)
...
Enter fullscreen mode Exit fullscreen mode

Now the date that you click will be shown at the bottom of calendar. if you look at your browser you will see this calendar:

Alt Text

That's it, Now you have the basic calendar, You can customise it as you like. That's all from me.

Can get the full code for /App.js at this gist here

Feedback appreciated.

Cover Image credit to @esteejanssens

Top comments (2)

Collapse
 
vins14432602 profile image
vins14432602

Hi,

I absolutely loved your work. I just want to know how can I remove the extra dates I am getting in a particular Month.

Collapse
 
aibrahim3546 profile image
ibrahim

Hey,

Sorry for the late response. unfortunately for now the library doesn't support it yet.
You can use javascript to trim it out for now manually. Contribution are welcome if you would like to add the feature. Thanks.

Cheers.