DEV Community

Cover image for React Calendar: A Guide to Building Dynamic Calendars with Events and Todos
Martin Persson
Martin Persson

Posted on

React Calendar: A Guide to Building Dynamic Calendars with Events and Todos

Table of Contents

Introduction

Links:

Live demo

Github repo

This tutorial aims to guide you through the process of building a calendar application using React and Material UI. With features like dynamic event creation, todo integration, and event color-coding, the application serves as a practical example of how to implement complex functionalities in a React app. We will use a variety of libraries and dependencies to achieve this, including Material UI components, React Big Calendar, and the date-fns library for localized date formats.

You can easily add on to this by integrate a database or add more info to your events.

Key Features

Event Management

  • Enables users to add, view, and manage events on the calendar.
  • Allows for both slot-based and date-based event creation.

Todo List Integration

  • Demonstrates how to integrate a todo list with a calendar application.
  • Features options for creating todos and associating them with events.

Event Color-Coding

  • Shows how to dynamically color-code events based on associated todos or other criteria.

React Big Calendar

  • Uses React Big Calendar library for main calendar functionalities.

Locale Support

  • Includes localized date formatting through the date-fns library.

Setting up the Project

In this section, we'll walk you through the initial setup for building the calendar application. This involves initializing a new Next.js project and installing the necessary dependencies.

To start, let's create a new React project. Open your terminal and run the following command:

npx create-react-app react-calendar --template typescript
Enter fullscreen mode Exit fullscreen mode

This will create a new directory called react-calendar with all the files you need. To navigate into your new app and start it, run:

cd react-calendar
npm run start
Enter fullscreen mode Exit fullscreen mode

Your React application should now be accessible at http://localhost:3000/

Installing Dependencies
To build our calendar application, several packages are required. These include:

  • Material-UI components (@mui/material and @mui/icons-material) for the UI.
  • Emotion (@emotion/react and @emotion/styled) for additional styling capabilities.
  • Date-fns (date-fns) for date manipulations.
  • React Big Calendar (react-big-calendar) for the main calendar functionalities.
  • React Colorful (react-colorful) for a color picker.

Install these dependencies by running the following command in your terminal:

MUI and Emotion

npm install @mui/material@5.14.6 @mui/icons-material@5.14.6 @emotion/react@11.11.1 @emotion/styled@11.11.0
Enter fullscreen mode Exit fullscreen mode

Date manipulation, calendar functionality, and a color picker.

npm install date-fns@2.30.0 react-big-calendar@1.8.2 react-colorful@5.6.1 @mui/x-date-pickers@5.0.12 @types/react-big-calendar
Enter fullscreen mode Exit fullscreen mode

After installing these dependencies, your project will have all the required packages to proceed with development. In the following sections, we'll dive into creating the various features for our calendar application.

Create the Calendar

we'll go step by step through creating a calendar in React using Typescript. The calendar will have features for creating events, and todos.

First, you'll need to create a file called EventCalendar.tsx in your src/components directory. This file will contain all the logic for your calendar application.

Note: As we add the code in chunks, you might see some errors initially. Don't worry, these will go away as we add more code.

Your overall folder structure should look like this:

src/components

 AddDatePickerEventModal.tsx
 AddEventModal.tsx
 AddTodoModal.tsx
 EventCalendar.tsx
 EventInfo.tsx
 EventInfoModal.tsx
Enter fullscreen mode Exit fullscreen mode

Importing Dependencies

In your EventCalendar.tsx file, add the following code to import all necessary dependencies:

import { useState, MouseEvent } from "react"
import { Box, Button, ButtonGroup, Card, CardContent, CardHeader, Container, Divider } from "@mui/material"

import { Calendar, type Event, dateFnsLocalizer } from "react-big-calendar"

import format from "date-fns/format"
import parse from "date-fns/parse"
import startOfWeek from "date-fns/startOfWeek"
import getDay from "date-fns/getDay"
import enUS from "date-fns/locale/en-US"

import "react-big-calendar/lib/css/react-big-calendar.css"

import EventInfo from "./EventInfo"
import AddEventModal from "./AddEventModal"
import EventInfoModal from "./EventInfoModal"
import { AddTodoModal } from "./AddTodoModal"
import AddDatePickerEventModal from "./AddDatePickerEventModal"
Enter fullscreen mode Exit fullscreen mode

Here, we're importing everything we'll need for our calendar, including React hooks, material-UI components, and functionalities from react-big-calendar.

Initialize the Localizer

Next, we'll initialize the date-fns localizer to handle date formatting.

const locales = {
  "en-US": enUS,
}

const localizer = dateFnsLocalizer({
  format,
  parse,
  startOfWeek,
  getDay,
  locales,
})
Enter fullscreen mode Exit fullscreen mode

This sets up date-fns to work with react-big-calendar.

Defining Data Models

We define our data models and utility functions like generateId to uniquely identify each todo and event.

export interface ITodo {
  _id: string
  title: string
  color?: string
}

export interface IEventInfo extends Event {
  _id: string
  description: string
  todoId?: string
}

export interface EventFormData {
  description: string
  todoId?: string
}

export interface DatePickerEventFormData {
  description: string
  todoId?: string
  allDay: boolean
  start?: Date
  end?: Date
}

export const generateId = () => (Math.floor(Math.random() * 10000) + 1).toString()
Enter fullscreen mode Exit fullscreen mode

Here, we define different interfaces for Events and Todos, along with some utility types.

Initialize Form Data

// Make this better
We can add events in 2 different ways, the first is that we click and drag in the calender and the other is what we use a datePicker. Lets create the initial formData for both options

const initialEventFormState: EventFormData = {
  description: "",
  todoId: undefined,
}

const initialDatePickerEventFormData: DatePickerEventFormData = {
  description: "",
  todoId: undefined,
  allDay: false,
  start: undefined,
  end: undefined,
}
Enter fullscreen mode Exit fullscreen mode

Managing States

Next, let's declare all the state variables we'll be using:

const [openSlot, setOpenSlot] = useState(false)
  const [openDatepickerModal, setOpenDatepickerModal] = useState(false)
  const [openTodoModal, setOpenTodoModal] = useState(false)
  const [currentEvent, setCurrentEvent] = useState<Event | IEventInfo | null>(null)

  const [eventInfoModal, setEventInfoModal] = useState(false)

  const [events, setEvents] = useState<IEventInfo[]>([])
  const [todos, setTodos] = useState<ITodo[]>([])

  const [eventFormData, setEventFormData] = useState<EventFormData>(initialEventFormState)

  const [datePickerEventFormData, setDatePickerEventFormData] =
    useState<DatePickerEventFormData>(initialDatePickerEventFormData)
Enter fullscreen mode Exit fullscreen mode

These states help us control modals, hold the current events, and keep track of todos.

Next up we can add our handlerFunctions,

Handling Events and Modals

We define several handler functions for handling slots, closing modals, and more.

 const handleSelectSlot = (event: Event) => {
    setOpenSlot(true)
    setCurrentEvent(event)
  }

  const handleSelectEvent = (event: IEventInfo) => {
    setCurrentEvent(event)
    setEventInfoModal(true)
  }

  const handleClose = () => {
    setEventFormData(initialEventFormState)
    setOpenSlot(false)
  }

  const handleDatePickerClose = () => {
    setDatePickerEventFormData(initialDatePickerEventFormData)
    setOpenDatepickerModal(false)
  }
Enter fullscreen mode Exit fullscreen mode

These functions are called when certain actions are taken in our application, like selecting an event or a time slot in the calendar.

Creating Events

Here is the core logic for adding new events to our calendar:

const onAddEvent = (e: MouseEvent<HTMLButtonElement>) => {
    e.preventDefault()

    const data: IEventInfo = {
      ...eventFormData,
      _id: generateId(),
      start: currentEvent?.start,
      end: currentEvent?.end,
    }

    const newEvents = [...events, data]

    setEvents(newEvents)
    handleClose()
  }

  const onAddEventFromDatePicker = (e: MouseEvent<HTMLButtonElement>) => {
    e.preventDefault()

    const addHours = (date: Date | undefined, hours: number) => {
      return date ? date.setHours(date.getHours() + hours) : undefined
    }

    const setMinToZero = (date: any) => {
      date.setSeconds(0)

      return date
    }

    const data: IEventInfo = {
      ...datePickerEventFormData,
      _id: generateId(),
      start: setMinToZero(datePickerEventFormData.start),
      end: datePickerEventFormData.allDay
        ? addHours(datePickerEventFormData.start, 12)
        : setMinToZero(datePickerEventFormData.end),
    }

    const newEvents = [...events, data]

    setEvents(newEvents)
    setDatePickerEventFormData(initialDatePickerEventFormData)
  }

  const onDeleteEvent = () => {
    setEvents(() => [...events].filter((e) => e._id !== (currentEvent as IEventInfo)._id!))
    setEventInfoModal(false)
  }
Enter fullscreen mode Exit fullscreen mode

These functions add and delete events to our state, whether they're created by dragging on the calendar or via the date picker.

Putting It All Together

Finally, we render our UI components and tie everything together:

return (
    <Box
      mt={2}
      mb={2}
      component="main"
      sx={{
        flexGrow: 1,
        py: 8,
      }}
    >
      <Container maxWidth={false}>
        <Card>
          <CardHeader title="Calendar" subheader="Create Events and Todos and manage them easily" />
          <Divider />
          <CardContent>
            <Box sx={{ display: "flex", justifyContent: "space-between" }}>
              <ButtonGroup size="large" variant="contained" aria-label="outlined primary button group">
                <Button onClick={() => setOpenDatepickerModal(true)} size="small" variant="contained">
                  Add event
                </Button>
                <Button onClick={() => setOpenTodoModal(true)} size="small" variant="contained">
                  Create todo
                </Button>
              </ButtonGroup>
            </Box>
            <Divider style={{ margin: 10 }} />
            <AddEventModal
              open={openSlot}
              handleClose={handleClose}
              eventFormData={eventFormData}
              setEventFormData={setEventFormData}
              onAddEvent={onAddEvent}
              todos={todos}
            />
            <AddDatePickerEventModal
              open={openDatepickerModal}
              handleClose={handleDatePickerClose}
              datePickerEventFormData={datePickerEventFormData}
              setDatePickerEventFormData={setDatePickerEventFormData}
              onAddEvent={onAddEventFromDatePicker}
              todos={todos}
            />
            <EventInfoModal
              open={eventInfoModal}
              handleClose={() => setEventInfoModal(false)}
              onDeleteEvent={onDeleteEvent}
              currentEvent={currentEvent as IEventInfo}
            />
            <AddTodoModal
              open={openTodoModal}
              handleClose={() => setOpenTodoModal(false)}
              todos={todos}
              setTodos={setTodos}
            />
            <Calendar
              localizer={localizer}
              events={events}
              onSelectEvent={handleSelectEvent}
              onSelectSlot={handleSelectSlot}
              selectable
              startAccessor="start"
              components={{ event: EventInfo }}
              endAccessor="end"
              defaultView="week"
              eventPropGetter={(event) => {
                const hasTodo = todos.find((todo) => todo._id === event.todoId)
                return {
                  style: {
                    backgroundColor: hasTodo ? hasTodo.color : "#b64fc8",
                    borderColor: hasTodo ? hasTodo.color : "#b64fc8",
                  },
                }
              }}
              style={{
                height: 900,
              }}
            />
          </CardContent>
        </Card>
      </Container>
    </Box>
  )
Enter fullscreen mode Exit fullscreen mode

Modals

Creating Different Modals for Events and Todos
Let's dive into the various modals that allow us to add new events and todos within our calendar application.

  • EventInfoModal: This modal serves as an information panel that displays details about selected events. It provides users with a quick glance at the event description and offers an option to delete the event if necessary.
  • AddDatePickerEventModal: This modal is specifically designed for adding new events to the calendar. It comes equipped with date and time pickers for specifying the event's start and end times and also allows the user to mark the event as an all-day event. A related todo can also be associated with the event for better task management.
  • AddEventModal: This modal allows users to create new events with essential information such as event description, time, and date. It acts as a simplified version of AddDatePickerEventModal, designed for quick event creation.
  • AddTodoModal: Beyond events, the application also supports the addition of 'todos'. This modal allows users to create new todos, complete with a title and a color code for easy identification. Users can also delete existing todos from within this modal.

AddEventModal.tsx

We start with AddEventModal.tsx. This modal gets triggered when you click and drag on a specific time range in the calendar UI. Place this file in the same folder (src/components) where you have EventCalendar.tsx.

Importing Dependencies

First, we import various modules that we will be using, such as the Material UI components and the custom types for our application.

import { ChangeEvent, Dispatch, MouseEvent, SetStateAction } from "react"
import {
  TextField,
  Dialog,
  DialogActions,
  DialogContent,
  DialogContentText,
  DialogTitle,
  Button,
  Autocomplete,
  Box,
} from "@mui/material"
import { EventFormData, ITodo } from "./EventCalendar"
Enter fullscreen mode Exit fullscreen mode

Component Props

We define the properties that this modal component expects using TypeScript's interface system. These props include booleans to control the modal's open state, functions to handle changes, and an array of Todo items.

interface IProps {
  open: boolean
  handleClose: Dispatch<SetStateAction<void>>
  eventFormData: EventFormData
  setEventFormData: Dispatch<SetStateAction<EventFormData>>
  onAddEvent: (e: MouseEvent<HTMLButtonElement>) => void
  todos: ITodo[]
}
Enter fullscreen mode Exit fullscreen mode

Component Functionality

Within the functional component, we define the actual behavior of the modal:

  • onClose: A function that calls the handleClose prop to close the modal.
  • onChange: A function that handles changes in the text fields, updating the eventFormData state.
  • handleTodoChange: A function that handles the selection of Todo items from the dropdown and updates the eventFormData.

The main return block of the component uses Material-UI components to render the modal's UI. It includes text fields for the event description and an autocomplete dropdown for Todo selection.

const AddEventModal = ({ open, handleClose, eventFormData, setEventFormData, onAddEvent, todos }: IProps) => {
  const { description } = eventFormData

  const onClose = () => handleClose()

  const onChange = (event: ChangeEvent<HTMLInputElement>) => {
    setEventFormData((prevState) => ({
      ...prevState,
      [event.target.name]: event.target.value,
    }))
  }

  const handleTodoChange = (e: React.SyntheticEvent, value: ITodo | null) => {
    setEventFormData((prevState) => ({
      ...prevState,
      todoId: value?._id,
    }))
  }

  return (
    <Dialog open={open} onClose={onClose}>
      <DialogTitle>Add event</DialogTitle>
      <DialogContent>
        <DialogContentText>To add a event, please fill in the information below.</DialogContentText>
        <Box component="form">
          <TextField
            name="description"
            value={description}
            margin="dense"
            id="description"
            label="Description"
            type="text"
            fullWidth
            variant="outlined"
            onChange={onChange}
          />
          <Autocomplete
            onChange={handleTodoChange}
            disablePortal
            id="combo-box-demo"
            options={todos}
            sx={{ marginTop: 4 }}
            getOptionLabel={(option) => option.title}
            renderInput={(params) => <TextField {...params} label="Todo" />}
          />
        </Box>
      </DialogContent>
      <DialogActions>
        <Button color="error" onClick={onClose}>
          Cancel
        </Button>
        <Button disabled={description === ""} color="success" onClick={onAddEvent}>
          Add
        </Button>
      </DialogActions>
    </Dialog>
  )
}

export default AddEventModal
Enter fullscreen mode Exit fullscreen mode

EventInfoModal

The EventInfoModal.tsx component serves as an informational pop-up that appears when a user clicks on an existing event within the calendar. This modal allows users to view the details of the selected event and provides an option to delete it. Like the AddEventModal.tsx, this file should also be placed in the src/components folder.

At the beginning of the file, you'll find a series of imports for React, Material UI components, and custom types.

The IProps interface defines the properties that this component expects. It includes a boolean flag to indicate whether the modal is open or not, a handleClose function to close the modal, a onDeleteEvent function to delete the current event, and the data for the current event.

import { SetStateAction, MouseEvent, Dispatch } from "react"
import { Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, Button, Box, Typography } from "@mui/material"
import { IEventInfo } from "./EventCalendar"

interface IProps {
  open: boolean
  handleClose: Dispatch<SetStateAction<void>>
  onDeleteEvent: (e: MouseEvent<HTMLButtonElement>) => void
  currentEvent: IEventInfo | null
}

const EventInfoModal = ({ open, handleClose, onDeleteEvent, currentEvent }: IProps) => {
  const onClose = () => {
    handleClose()
  }

  return (
    <Dialog open={open} onClose={onClose}>
      <DialogTitle>Event Info</DialogTitle>
      <DialogContent>
        <DialogContentText>
          <Typography sx={{ fontSize: 14, marginTop: 3 }} color="text.secondary" gutterBottom>
            {currentEvent?.description}
          </Typography>
        </DialogContentText>
        <Box component="form"></Box>
      </DialogContent>
      <DialogActions>
        <Button color="error" onClick={onClose}>
          Cancel
        </Button>
        <Button color="info" onClick={onDeleteEvent}>
          Delete Event
        </Button>
      </DialogActions>
    </Dialog>
  )
}

export default EventInfoModal
Enter fullscreen mode Exit fullscreen mode

And that concludes our EventInfoModal.tsx. This component serves as a crucial piece in providing a user-friendly calendar application by allowing users to quickly view and manage their events.

AddDatePickerEventModal

The AddDatePickerEventModal.tsx component is a specialized modal for adding events with date and time selections. This modal comes into play when you want to add an event that requires precise start and end times. It also features a checkbox to indicate if the event spans the entire day. Like the other modal components, place this file in the src/components directory.

Importing Dependencies

The top part of the file imports required modules from React, Material UI, and your custom types.

import React, { Dispatch, MouseEvent, SetStateAction, ChangeEvent } from "react"
import {
  TextField,
  Dialog,
  DialogActions,
  DialogContent,
  DialogContentText,
  DialogTitle,
  Button,
  Autocomplete,
  Box,
  Checkbox,
  Typography,
} from "@mui/material"
import { LocalizationProvider, DateTimePicker } from "@mui/x-date-pickers"
import { AdapterDateFns } from "@mui/x-date-pickers/AdapterDateFns"
import { DatePickerEventFormData, ITodo } from "./EventCalendar"
Enter fullscreen mode Exit fullscreen mode

Component Props

We define the expected properties in the IProps interface. These props include:

  • open: Boolean flag indicating if the modal is open.
  • handleClose: Function to close the modal.
  • datePickerEventFormData: Object containing form data for the event.
  • setDatePickerEventFormData: Function to update the form data.
  • onAddEvent: Function to add a new event.
  • todos: Array of Todo items.
interface IProps {
  open: boolean
  handleClose: Dispatch<SetStateAction<void>>
  datePickerEventFormData: DatePickerEventFormData
  setDatePickerEventFormData: Dispatch<SetStateAction<DatePickerEventFormData>>
  onAddEvent: (e: MouseEvent<HTMLButtonElement>) => void
  todos: ITodo[]
}
Enter fullscreen mode Exit fullscreen mode

Component Functionality

Within AddDatePickerEventModal, several key functions are defined:

  • onClose: Closes the modal by calling handleClose.
  • onChange: Handles text input changes and updates the form data.
  • handleCheckboxChange: Updates the allDay flag in the form data.
  • handleTodoChange: Handles the Todo selection.
  • isDisabled: Function to check if the "Add" button should be disabled based on the form data.

The main rendering block uses Material-UI components to render the UI, including date and time pickers.

const AddDatePickerEventModal = ({
  open,
  handleClose,
  datePickerEventFormData,
  setDatePickerEventFormData,
  onAddEvent,
  todos,
}: IProps) => {
  const { description, start, end, allDay } = datePickerEventFormData

  const onClose = () => {
    handleClose()
  }

  const onChange = (event: ChangeEvent<HTMLInputElement>) => {
    setDatePickerEventFormData((prevState) => ({
      ...prevState,
      [event.target.name]: event.target.value,
    }))
  }

  const handleCheckboxChange = (event: ChangeEvent<HTMLInputElement>) => {
    setDatePickerEventFormData((prevState) => ({
      ...prevState,
      allDay: event.target.checked,
    }))
  }

  const handleTodoChange = (e: React.SyntheticEvent, value: ITodo | null) => {
    setDatePickerEventFormData((prevState) => ({
      ...prevState,
      todoId: value?._id,
    }))
  }

  const isDisabled = () => {
    const checkend = () => {
      if (!allDay && end === null) {
        return true
      }
    }
    if (description === "" || start === null || checkend()) {
      return true
    }
    return false
  }

  return (
    <Dialog open={open} onClose={onClose}>
      <DialogTitle>Add event</DialogTitle>
      <DialogContent>
        <DialogContentText>To add a event, please fill in the information below.</DialogContentText>
        <Box component="form">
          <TextField
            name="description"
            value={description}
            margin="dense"
            id="description"
            label="Description"
            type="text"
            fullWidth
            variant="outlined"
            onChange={onChange}
          />
          <LocalizationProvider dateAdapter={AdapterDateFns}>
            <Box mb={2} mt={5}>
              <DateTimePicker
                label="Start date"
                value={start}
                ampm={true}
                minutesStep={30}
                onChange={(newValue) =>
                  setDatePickerEventFormData((prevState) => ({
                    ...prevState,
                    start: new Date(newValue!),
                  }))
                }
                renderInput={(params) => <TextField {...params} />}
              />
            </Box>

            <Box>
              <Typography variant="caption" color="text" component={"span"}>
                All day?
              </Typography>
              <Checkbox onChange={handleCheckboxChange} value={allDay} />
            </Box>

            <DateTimePicker
              label="End date"
              disabled={allDay}
              minDate={start}
              minutesStep={30}
              ampm={true}
              value={allDay ? null : end}
              onChange={(newValue) =>
                setDatePickerEventFormData((prevState) => ({
                  ...prevState,
                  end: new Date(newValue!),
                }))
              }
              renderInput={(params) => <TextField {...params} />}
            />
          </LocalizationProvider>
          <Autocomplete
            onChange={handleTodoChange}
            disablePortal
            id="combo-box-demo"
            options={todos}
            sx={{ marginTop: 4 }}
            getOptionLabel={(option) => option.title}
            renderInput={(params) => <TextField {...params} label="Todo" />}
          />
        </Box>
      </DialogContent>
      <DialogActions>
        <Button color="error" onClick={onClose}>
          Cancel
        </Button>
        <Button disabled={isDisabled()} color="success" onClick={onAddEvent}>
          Add
        </Button>
      </DialogActions>
    </Dialog>
  )
}

export default AddDatePickerEventModal
Enter fullscreen mode Exit fullscreen mode

AddDatePickerEventModal.tsx is an essential component that brings enhanced functionality to the calendar application. It provides a way to input more detailed events with start and end times or mark them as all-day events.

AddTodoModal

AddTodoModal.tsx is another crucial component in your calendar application, serving as the interface for users to add new "Todo" items. It integrates various UI components and logic to facilitate adding new Todos, deleting existing ones, and customizing the color of each Todo.

Importing Dependencies

The component imports a mixture of built-in React hooks, Material UI components, and your custom types to build its functionality:

import { useState, Dispatch, SetStateAction } from "react"
import {
  Dialog,
  DialogActions,
  DialogContent,
  DialogContentText,
  DialogTitle,
  Box,
  Button,
  Divider,
  IconButton,
  List,
  ListItem,
  ListItemText,
  TextField,
} from "@mui/material"
import DeleteIcon from "@mui/icons-material/Delete"

import { HexColorPicker } from "react-colorful"
import { ITodo, generateId } from "./EventCalendar"
Enter fullscreen mode Exit fullscreen mode

Component Props
The props for this modal include:

  • open: Boolean flag to control the visibility of the modal.
  • handleClose: Function to close the modal.
  • todos: Array of existing Todos.
  • setTodos: Function to set the new list of Todos.
interface IProps {
  open: boolean
  handleClose: Dispatch<SetStateAction<void>>
  todos: ITodo[]
  setTodos: Dispatch<SetStateAction<ITodo[]>>
}
Enter fullscreen mode Exit fullscreen mode

Rendering and Layout

The component leverages Material UI components for layout and styling. Notable features include:

  • A color picker (HexColorPicker) for choosing the color of a Todo.
  • A TextField for entering the title of the Todo.
  • A List of existing Todos with delete buttons.
  • Cancel and Add buttons in the Dialog actions.

Event Handling

Event handlers are attached to the form field for updating the title (setTitle) and the color picker (setColor), along with click handlers for adding (onAddTodo) and deleting (onDeleteTodo) Todos. The Add button is disabled if either the title or color is empty.

export const AddTodoModal = ({ open, handleClose, todos, setTodos }: IProps) => {
  const [color, setColor] = useState("#b32aa9")
  const [title, setTitle] = useState("")

  const onAddTodo = () => {
    setTitle("")
    setTodos([
      ...todos,
      {
        _id: generateId(),
        color,
        title,
      },
    ])
  }

  const onDeletetodo = (_id: string) => setTodos(todos.filter((todo) => todo._id !== _id))

  const onClose = () => handleClose()

  return (
    <Dialog open={open} onClose={onClose}>
      <DialogTitle>Add todo</DialogTitle>
      <DialogContent>
        <DialogContentText>Create todos to add to your Calendar.</DialogContentText>
        <Box>
          <TextField
            name="title"
            autoFocus
            margin="dense"
            id="title"
            label="Title"
            type="text"
            fullWidth
            sx={{ mb: 6 }}
            required
            variant="outlined"
            onChange={(e) => {
              setTitle(e.target.value)
            }}
            value={title}
          />
          <Box sx={{ display: "flex", justifyContent: "space-around" }}>
            <HexColorPicker color={color} onChange={setColor} />
            <Box sx={{ height: 80, width: 80, borderRadius: 1 }} className="value" style={{ backgroundColor: color }}></Box>
          </Box>
          <Box>
            <List sx={{ marginTop: 3 }}>
              {todos.map((todo) => (
                <ListItem
                  key={todo.title}
                  secondaryAction={
                    <IconButton onClick={() => onDeletetodo(todo._id)} color="error" edge="end">
                      <DeleteIcon />
                    </IconButton>
                  }
                >
                  <Box
                    sx={{ height: 40, width: 40, borderRadius: 1, marginRight: 1 }}
                    className="value"
                    style={{ backgroundColor: todo.color }}
                  ></Box>
                  <ListItemText primary={todo.title} />
                </ListItem>
              ))}
            </List>
          </Box>
        </Box>
      </DialogContent>
      <Divider />
      <DialogActions sx={{ marginTop: 2 }}>
        <Button sx={{ marginRight: 2 }} variant="contained" color="error" onClick={onClose}>
          Cancel
        </Button>
        <Button
          onClick={() => onAddTodo()}
          disabled={title === "" || color === ""}
          sx={{ marginRight: 2 }}
          variant="contained"
          color="success"
        >
          Add
        </Button>
      </DialogActions>
    </Dialog>
  )
}

Enter fullscreen mode Exit fullscreen mode

EventInfo

EventInfo.tsx is a straightforward yet essential component within your calendar application. It displays information about individual events. Right now we only show the description but you could add more things if you like.

import { Typography } from "@mui/material"
import { IEventInfo } from "./EventCalendar"

interface IProps {
  event: IEventInfo
}

const EventInfo = ({ event }: IProps) => {
  return (
    <>
      <Typography>{event.description}</Typography>
    </>
  )
}

export default EventInfo
Enter fullscreen mode Exit fullscreen mode

Conclusion

Congratulations! You've successfully built a dynamic calendar application using React, Material UI, and Typescript. We started from scratch, setting up our project environment and installing the necessary dependencies. Then, we dived into the core functionalities, covering everything from event management and todo integration to color coding and localization.

The application you've built is robust and feature-rich, thanks to the various libraries and components we've employed. You now have the skills to:

  • Create and manage events on a calendar.
  • Integrate a todo list within a calendar application.
  • Color-code events based on associated todos or other criteria.
  • Utilize the React Big Calendar library for additional functionalities.
  • Apply localized date formats with the date-fns library.

Not only does this application serve as a practical tool, but it's also a great foundation for any future projects requiring calendar or scheduling functionalities. Feel free to expand upon this by adding more features or tweaking the existing ones to better suit your needs.

Thank you for following along, and happy coding!

Top comments (5)

Collapse
 
mynameistakenomg profile image
Sean_Fang

Thank you so much for posting this great guide 👍 I've been working on a project which is about booking appointments, and I learned a lot by reading this post. Now I feel more confident of working on my project. Thank you again, really appreciate it!

Collapse
 
martinpersson profile image
Martin Persson • Edited

Thank you! I'm so glad to hear that you found the guide helpful. Best of luck with your project 😁

Collapse
 
codluca profile image
codluca

Check out HexaFlexa Timegrid. It's free for personal use and with low price for commercial projects. It has features like swiping, event drag-and-drop and resizing, resource support, and easy integration with Angular, React, and Vue. See demos and documentation at hexaflexa.com

Collapse
 
estt profile image
estt

hello, thanks for your tutorial. i liked the demo and tried recreate this project myself. but i was not sucessful.

then i downloaded source code from git. i found out that two last line were missing in eventcalendar.tsx. and i also had to rewrite the default app.tsx file.

but still it is not working for me. i cant see anything @ localhost. it looks like calling the eventcalendar does nothing.

can anyone help me please? thanks in advance

Collapse
 
martinpersson profile image
Martin Persson

Hi, Sorry to hear you are having trouble!

Do you see any error messages you can share?