Search justacoding.blog. [Enter] to search. Click anywhere to close.

October 31st, 2021

React Calendar Component Example (With Events)

In this article, we’ll go over the build of a React calendar component. This calendar can also display events.

You can view the React calendar component example in question directly in JSFiddle.

If you’re fairly new to React, don’t worry — building a React calendar component is fairly straightforward. It is, in fact, much easier than it looks.

Throughout the calendar build, some important JavaScript fundamentals were utilized, such as working with the Date object.

Alongside that, functional React components were leveraged to build up a neat, clean React calendar app that’s easy to work with and easy to scale up.

This article is something of a React calendar tutorial; it acts as an accompaniment to the app example provided above, whereby the key aspects and points of the build process will be explained and discussed in detail.

Before we begin, a disclaimer…

This component is not “production ready” – and it’s not intended to be used directly as such. It’s simply a foundation which I’m using to serve as an example of how you can implement this kind of events calendar-based functionality for yourself.

Of course, feel free to use this code — but do remember to add in the various missing pieces you’ll likely be needing, too.

So, with that in mind, let’s begin!

What the React calendar component actually does

The calendar component is fairly basic. It’s a standard calendar that is also capable of displaying events. So in some ways, it can also be utilized as the start of a more sophisticated React calendar scheduler component, too.

Here’s a rundown of the component’s functionalities:

  • Display a monthly calendar view
  • Navigate through the months
  • Display “events” within the calendar based on an array of JavaScript objects (or alternatively, some parsed JSON input if required)
  • Create new events within the calendar. Note: we won’t have any backend functionality here; so these events won’t be persisted in any way between calendar refreshes (likewise for editing and deleting!)
  • Edit events
  • Delete events
  • We’ll also touch on some UX best practices like greying out or disabling past days, making the calendar responsive and so on

With that out of the way, let’s discuss the build itself.

Creating the grid of calendar days

The grid of days is one of the most fundamental aspects of our React calendar component. And as such, this grid of days forms the basis of our implementation. Other functionalities revolve around interacting with this grid in some way — adding events or removing events from it, for instance.

Before we can output any structure in this regard, we must first determine which dates should comprise our selected calendar month.

Gathering an array of dates

A single month within this calendar component contains 5 rows consisting of 7 cells (days) within each. These cells obviously represent each day of the month, broken down into weekly rows as expected.

To construct this grid, it was first necessary to work out the “first” day that should be visible for each given month.

So in this case, that would be the first Monday before our month start day (or the start day itself, if that particular day lands on a Monday too) — as I’ve decided that my calendar weeks will start on Mondays.

// gathering an array of dates to populate the selected month
// part of the Grid component

const startingDate = new Date(date.getFullYear(), date.getMonth(), 1)
startingDate.setDate(startingDate.getDate() - (startingDate.getDay() - 1))

const dates = []
for (let i = 0; i < (ROWS_COUNT * 7); i++) {
    const date = new Date(startingDate)
    dates.push({ date, events: findEventsForDate(events, date) })
    startingDate.setDate(startingDate.getDate() + 1)
}

If you’re wanting to use a different starting day (ie. Sunday), your logic will need to account for that day instead (you’ll offset the first day of the month differently).

With a reference to our first day, it’s then a matter of iterating through the subsequent days until we have a full month’s worth of dates (or more specifically, a full month’s worth of JavaScript Date objects).

The aim here is to end up with an array containing all date objects that should be utilized based upon the selected month.

This array of dates can subsequently be used in the next step, where we’ll actually create some output in JSX.

Outputting the days within the grid

With the array of date objects, it’s simply a matter of iterating through each and returning the desired output.

In this case, we can make use of a map.

We can map on the dates array to populate the elements that make up each “cell” or day, so that includes regular HTML output as well as any child components.

// using the date objects stored in the dates array to
// build up the required output within JSX
// part of the Grid component

{dates.map((date, index) => {
  return (
    <div 
      key={index}
      className={`cell 
        ${date.date.getTime() == currentDate.getTime() ? 
          "current" : ""}
        ${date.date.getMonth() != actualDate.getMonth() ? 
          "otherMonth" : ""}`
      }>
        <div className="date">
          {date.date.getDate()}
        </div>
        {date.events.map((event, index) => 
          <MiniEvent 
            key={index} 
            event={event} 
            setViewingEvent={setViewingEvent} 
          />
        )}
    </div>
  )
})}

You’ll notice I’m not only storing Date objects in this array, but also events data. Don’t worry, we’ll get to that part specifically later on.

Selecting the current day

To select or highlight the “current” day within the calendar grid, we just need a simple comparison.

When comparing dates within JavaScript, we just need to look at the time property of each, using getTime. You can see how that works in the snippet above, specifically, with this line:

date.date.getTime() == currentDate.getTime()

If the day in question matches the current day; apply a special CSS class to style it differently.

Highlighting or indicating days outside of the current month

Rather than showing empty cells for days that don’t land in the target month, I thought it was better to treat these days like we’d treat days in the target month. So they’ll still be visible and still be capable of displaying events.

To be clear, these days are days that fall at the end of the previous month, or at the start of the next month.

It’s much more user-friendly to clearly indicate that these days fall outside of the target month rather than styling them like a regular day.

To do this, we can just make a quick comparison specifically on the month portion of the dates in question:

date.date.getMonth() != actualDate.getMonth()

Then as with highlighting the current day, it’s just a matter of applying a custom CSS class based on the results of this comparison.

Days that fall “outside” of the target month are greyed out slightly in my implementation.

Navigating through the months (via left and right arrows)

You’ll notice that our React calendar component accepts a month and year amongst it’s props. From these props, we store a resultant Date object in state.

To move either forwards or backwards to a different month, it’s simply a matter of adjusting this date (by either adding or subtracting one month from it in this case).

// the Navigation component in its entirety

const Navigation = ({ date, setDate, setShowingEventForm }) => {
  return (
    <div className="navigation">
      <div className="back" onClick={() => {
        const newDate = new Date(date)
        newDate.setMonth(newDate.getMonth() - 1)
        setDate(newDate)
      }}>
        {"<-"} {MONTHS[date.getMonth() == 0 ? 11 : date.getMonth() - 1]}
      </div>

      <div className="monthAndYear">
        {MONTHS[date.getMonth()]} {date.getFullYear()}
        <a href="javascript:;" 
          onClick={() => setShowingEventForm({ visible: true })}>
          +
        </a>
      </div>

      <div className="forward" onClick={() => {
        const newDate = new Date(date)
        newDate.setMonth(newDate.getMonth() + 1)
        setDate(newDate)
      }}>
        {MONTHS[date.getMonth() == 11 ? 0 : date.getMonth() + 1]} {"->"}
      </div>
    </div>
  )
}

That’s all we need to do to navigate through the calendar.

Other things we could do with regards to navigation

We could optionally add a nice “date picker” to quick-jump to a specific month/year combination if required.

Also, we could implement a “yearly” view for our calendar — so we don’t show a specific month, but rather, we show all of the months in the given year.

Any form of navigation within the calendar component can be implemented by primarily adjusting the month and year, with logic to handle what should happen in each case.

Displaying events in the calendar component

The calendar component also accepts an array of events, preloadedEvents — these represent event data that may be preloaded and hence don’t need to be fetched later on.

Once the initial base is in place (our grid of days/dates), providing the React calendar component with events is straightforward enough.

Parsing the event objects

To help with presenting events in their appropriate positions within the calendar (ie. what day they land on), I’ve decided to parse the events data to begin with.

In particular, I want to store the actual date objects for each event (date from and date to) as that’ll be much easier to work with for future operations (as opposed to having to manually retrieve this each time I need it).

// a simple utility/helper function to parse events data
// into the required format for the application to use

const parseEvents = (events) => {
  return events.map(event => {
    const from = new Date(event.dateFrom)
    const to = new Date(event.dateTo)

    return {
      ...event,
      from,
      to
    }
  })
}

Another benefit of parsing data like this means you can validate it should you need to. I’m not doing that here — but it’s an easy way to ensure bad data cannot break your application in some way (null pointer exceptions, invalid JSON and so on).

Logic to find events on a given date

Once we have the relevant date objects stored alongside each event, it’s a matter of using the simple logic stored inside findEventsForDate within the same loop that renders out our days:

// a helper function to search for events (stored in state)
// that occur on as specific date within the calendar

const findEventsForDate = (events, date) => {
  const dateTime = date.getTime()

  return events.filter(event => {
    const eventFromTime = toStartOfDay(event.from).getTime()
    const eventToTime = toStartOfDay(event.to).getTime()

    return (dateTime >= eventFromTime && dateTime <= eventToTime)
  })
}

With this, you’ll be able to present the event data within the React calendar component example!

So it’s a matter of checking if any events should display on the given day, and then outputting those events accordingly. We need to look for events the start on the given day, events that stop on the given day, or events that otherwise overlap the given day, too.

You can see this logic reflected in the function I have used, above.

So supplying the React calendar with events is simply a matter of:

  • storing those events in state
  • displaying or presenting those events on the day(s) they land on within the calendar grid using the logic above

And that’s it, the start of a (simple) scheduler component!

Creating new events and adding them to the calendar

Adding events to our React calendar component is as simple as modifying (or more specifically, adding to) our events state to include the new event data.

However, remember that this is a mock implementation.

In a real implementation, we’d have real network requests!

Of course, in a real world implementation, you’d modify the events state after a successful network request. So once we know we’ve successfully created the new event via our backend service — we can modify the events data then.

Of course, you would also handle any errors here too. The mock implementation obviously doesn’t cater for any of that stuff yet.

// from the Calendar component

const addEvent = (event) => {
  setIsLoading(true)
  setShowingEventForm({ visible: false })

  setTimeout(() => {
    const parsedEvents = parseEvents([event])
      
    const updatedEvents = [...events]
    updatedEvents.push(parsedEvents[0])

    setEvents(updatedEvents)
    setIsLoading(false)
    showFeedback({ message: "Event created successfully", type: "success" })
  }, MOCK_LOADING_TIME)
}

You can see I’m simulating loading in the relevant function (addEvent), and also for editing (editEvent) and deleting events (deleteEvent), too.

This is purely to mimic the UX the user would experience if a network request was taking place. Remember to swap that with a fetch or something similar in reality!

Back to handling the (newly) created events

It’s a good idea (actually, it’s required in this implementation) to parse the new event first.

This is because the calendar component relies on the time property of each event date to more easily decide where the event should show. This means generating Date objects from the user-supplied date string supplied.

Once the new event is added to the state, it’ll populate accordingly in the correct place within our calendar thanks to the magic of React and the logic outlined above.

Creating a generic modal for this type of operation

In terms of actually create the new event, I’ve created a generic Modal component that can be used. Within this modal, there is a simple form. The user can populate this form and submit the event as expected.

// a helper component, a simple modal to display content
// in an overlay/popup box

const Modal = ({ children, onClose, title, className }) => {
  return (
    <Fragment>
      <div className="overlay" onClick={onClose} />
      <div className={`modal ${className}`}>
        <h3>{title}</h3>
        <div className="inner">
          {children}
        </div>
      </div>
    </Fragment>
  )
}

In general, modals are useful for these type of interactions as they can be displayed over the top of other elements, and you don’t need to make specific space in the UI or navigate out to a new page.

add a new event to the calendar
The generic modal with the EventForm component nested inside

You’ll see I’m using this component to add or edit events. That’s because the component is basically the same for both operations. Given this, it makes sense to reuse it – applying simple logic to swap certain terminologies, actions and so on.

Keeping it immutable

You’ll notice we add events like so:

// a snippet from the Calendar component's addEvent function

const parsedEvents = parseEvents([event])

const updatedEvents = [...events]
updatedEvents.push(parsedEvents[0])

setEvents(updatedEvents)

If you’re new to React – this syntax may seem slightly unusual at first. Why are we doing it this way?

Immutability is an important aspect in programming in general. This is not specific to React or JavaScript. It’s a fundamental concept in programming that you should wrap your head around if you aren’t already familiar.

If something is immutable, that means it doesn’t change (or can’t be changed).

We want to treat our state in React as immutable.

That means: we don’t want to modify it directly. This can lead to a whole host of unfortunate, unwanted side-effects (which I won’t specifically go into here). Just know it can cause problems.

Basically, and in general, you’ll want to make your state updates immutable wherever possible. You’ll first want to copy the given object, update the copied object and then set this full, copied, mutated object to state accordingly.

Creating a new event on a specific day, directly

As you can see in the JSFiddle provided, it’s also possible to create an event on a specific date, by clicking the + next to the given date. This is a quicker route than clicking the + button at the top of the calendar — and it means the “from date” is preselected for the user, which is handy.

Again, we can utilize the same modal/component here — only pass in an extra prop, more specifically, preselectedDate.

Then it’s a case of looking for this preselectedDate prop, and populating the form accordingly if it’s set:

// some simple logic from the top of the EventForm component

const newEvent = withEvent || {}
if (!withEvent && !!preselectedDate) {
  newEvent.dateFrom = dateToInputFormat(preselectedDate)
}
const [event, setEvent] = useState(newEvent)

(I’m using a helper function here, dateToInputFormat, as the default datepicker-local input requires this.)

So as you can see, the EventForm is quite flexible at this point.

It can create a new event, either from a totally clean slate — or alternatively, with preselected data (from date).

It can also be used to edit existing events, too, which we’ll talk through next.

Editing an event

Editing events works much like adding events. We just need to modify the given event in state and let React do the rest.

We can use the same kind of pattern and UX to achieve this, leveraging the component we’ve already built to handle the adding of events.

To edit an event, I have utilized the same component that’s used to add events — only we need to pass in the event to edit this time. For this, we can use the withEvent prop.

The component should handle the rest based upon our inputs provided.

Here’s the function that would actually trigger the request to edit the event:

// from the Calendar component

const editEvent = (event) => {
    setIsLoading(true)
    setShowingEventForm({ visible: false })

    setTimeout(() => {
      const parsedEvent = parseEvents([event])
      
      const updatedEvents = [...events].map(updatedEvent => {
        return updatedEvent.id === event.id ? parsedEvent[0] : updatedEvent
      })

      setEvents(updatedEvents)
      setIsLoading(false)
      showFeedback({ message: "Event edited successfully", type: "success" })
    }, MOCK_LOADING_TIME)
  }

So we’d ultimately want a network request here, and we’d only modify the events state after as successful response from our backend service.

Deleting an event from the calendar

Lastly, to delete an event from our React calendar component we just need to remove the specific event from the events state.

A good way to use this is to filter the current events, ensuring that the target event is not returned in the new (filtered) array:

// from the Calendar component

const deleteEvent = (event) => {
  setIsLoading(true)
  setViewingEvent(null)

  setTimeout(() => {
    const updatedEvents = [...events].filter(finalEvent => finalEvent.id != event.id)
      
    setEvents(updatedEvents)
    setIsLoading(false)
    showFeedback({ message: "Event deleted successfully", type: "success" })
  }, MOCK_LOADING_TIME)
}

Again, it’s good to operate with immutability in mind.

Adding a loading spinner

Although the React calendar component example is fairly basic (and rough), I thought it was a nice touch at add some simple UX concept like this.

These type of features really help the usability and general overall aesthetic of your app.

In this case, within the <Loader /> component — I’m “mocking” the loading of a real HTTP request (with which you’d actually update your data so it persists between refreshes) with a simple setTimeout.

This gives us an opportunity to see the loading spinner in action, and to see how it’d function in the real world.

Adding a feedback panel

Similarly, I’ve added a mechanism to provide feedback to the user. This simple component is called <Feedback />.

It’s often important to do things in a consistent manner with applications like this. That’s why having a simple component to handle the feedback in each case is beneficial.

// presentational component used for display feedback of all types
// type can be passed to alter the styling/theme of the message

const Feedback = ({ message, type }) => {
  return (
    <div className={`feedback ${type}`}>{message}</div>
  )
}

In this case, I’ve got a simple prompt that pops up with the relevant message inside. Of course, the prompt can be tweaked (in terms of it’s colour scheme) to reflect different “types” of feedback (success, warning, error and so on).

Hiding the feedback dialogue

As I don’t want the feedback to stay visible indefinitely after it’s displayed, I have a simple function within Calendar to handle this behaviour for me:

// a simple helper function from within the Calendar component
// used to set feedback and then subsequently hide the dialogue, too

const showFeedback = ({ message, type, timeout = 2500 }) => {
  setFeedback({ message, type })
  setTimeout(() => {
    setFeedback(null)
  }, timeout)
}

So I can use this function whenever I need to show any type of feedback. It’ll always initially show the feedback dialogue to the user; then hide it again after a 2.5 second delay (by default).

Ideas for some natural enhancements to the React calendar component

This React calendar component example is fairly basic in this particular incarnation.

We can easily enhance it, however, by extrapolating on existing features as well as adding all new features in to the mix.

Here are some ideas in this regard:

  • Daily view
  • Weekly view
  • Yearly view
  • “Jump to” functionality – so you can enter a specific date (with a datepicker) and efficiently navigate to the given day

If you’re looking to build more of a React scheduler component, there are a lot of avenues we can go down also:

  • Add a “list” view of events, so the user can see the next 10 (or so) upcoming events regardless of month
  • Repeatable events – specify if a given event should re-occur at specific intervals (daily, monthly, yearly)
  • Email reminders and notifications, this one is more applicable to the backend implementation

With projects of this kind, you’ll find that if you have a good base or foundation, it’s much easier to iterate on top of it with new features and facilities.

In closing

We’ve walked through many of the key steps in creating a React calendar with events. I hope this article has been beneficial and of use to you!

I’ve tried to explain all of the “key” aspects to this particular implementation.

Although the React calendar component example would not necessarily be considered “production-ready” in many ways — I believe it does provide a good example to start with if building some calendar-oriented functionality in React is your goal.

Why is this React calendar example not production ready?

The code example provided is simply not a fully finished implementation.

It’s missing many cosmetic aspects, as well as many functional aspects (such as data validation, actual backend interactions and so on). As such, you should consider it to be like “pseudo-code” of sorts — useful to get an initial understanding of the task at hand, but not something you can just drag and drop into your own work.

With production code, you should never cut corners. Production code is not used to provide examples — it’s to fulfil a specific business requirement in the most robust and effective manner possible.

That said, I believe the calendar example provided could provide a good foundation for a more refined, production-ready React calendar app.

For more articles & example like this…

Stay tuned for more articles just like this one: explanations with accompanying JSFiddle (or equivalent) implementations.

Thanks for reading!

Have any questions? Ping me over at @justacodingblog
← Back to blog