Build a Simple Next.js Interactive Web-Based Event Calendar

Written by

in

In today’s fast-paced world, staying organized is key. Whether you’re managing personal appointments, coordinating team meetings, or planning a large event, a well-designed calendar can be an invaluable tool. This tutorial will guide you through building a simple, yet functional, interactive event calendar using Next.js, a powerful React framework for building web applications. We’ll cover everything from setting up your project to implementing features like adding, editing, and deleting events. By the end of this guide, you’ll have a solid understanding of how to create dynamic web applications with Next.js and a practical tool you can use or expand upon.

Why Build an Event Calendar with Next.js?

Next.js offers several advantages for building web applications, especially interactive ones like an event calendar:

  • Server-Side Rendering (SSR) and Static Site Generation (SSG): Next.js allows you to render your application on the server or generate static HTML files, improving SEO and initial load times.
  • Easy Routing: Next.js simplifies routing with its file-system-based routing, making it easy to create different pages for your calendar.
  • API Routes: Next.js provides a built-in API routes feature, allowing you to create backend endpoints within your Next.js application, simplifying the development process.
  • Optimized Performance: Next.js automatically optimizes your application for performance, including image optimization, code splitting, and more.
  • React Ecosystem: Since Next.js is built on React, you can leverage the vast React ecosystem of libraries and components.

Building an event calendar with Next.js is a great way to learn these concepts while creating a useful application. It’s also a fantastic project to showcase your skills and understanding of modern web development.

Prerequisites

Before we begin, make sure you have the following installed on your system:

  • Node.js and npm (or yarn): You’ll need Node.js and npm (Node Package Manager) or yarn to manage your project’s dependencies. You can download them from the official Node.js website.
  • A Code Editor: A code editor like Visual Studio Code (VS Code), Sublime Text, or Atom will be helpful for writing and editing your code.
  • Basic Understanding of HTML, CSS, and JavaScript: Familiarity with these web technologies is essential for understanding the code and concepts presented in this tutorial.

Setting Up Your Next.js Project

Let’s get started by creating a new Next.js project. Open your terminal and run the following command:

npx create-next-app my-event-calendar

This command will create a new Next.js project named “my-event-calendar”. Navigate into your project directory:

cd my-event-calendar

Now, let’s install some dependencies that we’ll need for our calendar:

npm install date-fns react-big-calendar --save

Here’s what these dependencies do:

  • date-fns: A modern JavaScript date utility library that provides a comprehensive set of functions for working with dates and times. We’ll use this for date formatting and manipulation.
  • react-big-calendar: A React component for displaying and interacting with a calendar. This will handle the calendar’s visual representation and user interactions.

Project Structure

Your project directory should look something like this:

my-event-calendar/
├── node_modules/
├── pages/
│   ├── _app.js
│   ├── index.js
│   └── api/
│       └── events.js # This will be created later
├── public/
├── .gitignore
├── next.config.js
├── package-lock.json
├── package.json
└── README.md
  • pages/: This directory contains your application’s pages. Each file in this directory represents a route in your application. For example, `index.js` corresponds to the `/` route.
  • public/: This directory is for static assets like images, fonts, etc.
  • _app.js: This file is the entry point for your application. You can use it to apply global styles, initialize components, and more.
  • api/: This directory is where you’ll create API routes.

Creating the Calendar Component

Let’s create the main calendar component. We’ll use `react-big-calendar` for the calendar’s display and functionality. Create a new file called `components/Calendar.js` in your project’s root directory. If the components folder doesn’t exist, create it.

// components/Calendar.js
import React, { useState, useEffect } from 'react';
import { Calendar, momentLocalizer } from 'react-big-calendar';
import moment from 'moment';
import 'react-big-calendar/lib/css/react-big-calendar.css';

const localizer = momentLocalizer(moment);

const MyCalendar = () => {
  const [events, setEvents] = useState([]);

  useEffect(() => {
    // Fetch events from your API endpoint (we'll create this later)
    const fetchEvents = async () => {
      try {
        const response = await fetch('/api/events');
        const data = await response.json();
        // Format the events to match react-big-calendar's event format
        const formattedEvents = data.map(event => ({
          ...event,
          start: new Date(event.start),
          end: new Date(event.end),
        }));
        setEvents(formattedEvents);
      } catch (error) {
        console.error('Error fetching events:', error);
      }
    };

    fetchEvents();
  }, []);

  return (
    <div>
      
    </div>
  );
};

export default MyCalendar;

Let’s break down this code:

  • Import Statements: We import necessary components from `react-big-calendar`, `moment`, and `useState` and `useEffect` from React. We also import the CSS for the calendar.
  • localizer: We initialize the localizer, which allows the calendar to handle date and time formatting based on your locale.
  • events state: We use the `useState` hook to manage the events data. Initially, it’s an empty array.
  • useEffect hook: The `useEffect` hook fetches the events data from the `/api/events` endpoint when the component mounts. We’ll create this API route later.
  • fetchEvents function: This asynchronous function fetches the events from the API. The fetched data is then formatted to match the format required by `react-big-calendar`.
  • Calendar Component: We render the `Calendar` component from `react-big-calendar`. We pass in the `localizer`, `events`, and the accessors for the event’s start time (`startAccessor`), end time (`endAccessor`), and title (`titleAccessor`). We also set the default view to “month” and define the calendar’s height.

Creating the API Route for Events

Now, let’s create the API route that will serve the event data. Create a new file called `pages/api/events.js`:


// pages/api/events.js
export default async function handler(req, res) {
  // In a real application, you'd fetch events from a database or external API
  const events = [
    {
      id: 1,
      title: 'Meeting with John',
      start: '2024-01-20T10:00:00.000Z',
      end: '2024-01-20T11:00:00.000Z',
    },
    {
      id: 2,
      title: 'Project Deadline',
      start: '2024-01-22T14:00:00.000Z',
      end: '2024-01-22T16:00:00.000Z',
    },
  ];

  res.status(200).json(events);
}

This code defines an API route at `/api/events`. For now, it returns a hardcoded array of events. In a production environment, you would fetch events from a database (like MongoDB, PostgreSQL, or Firebase) or an external API.

Integrating the Calendar Component into Your App

Now, let’s integrate the calendar component into your main page (`pages/index.js`). Replace the content of `pages/index.js` with the following code:


// pages/index.js
import React from 'react';
import MyCalendar from '../components/Calendar';

const Home = () => {
  return (
    <div>
      <h1>My Event Calendar</h1>
      
    </div>
  );
};

export default Home;

This code imports the `Calendar` component and renders it on the home page. Now, let’s add some basic styling to make it look better. Create a new file called `styles/globals.css` (or modify it if it already exists) and add the following CSS:


/* styles/globals.css */
body {
  font-family: sans-serif;
  margin: 0;
  padding: 20px;
}

.calendar-container {
  margin-top: 20px;
}

h1 {
  text-align: center;
}

To apply these styles, import `globals.css` in your `_app.js` file:


// pages/_app.js
import '../styles/globals.css';

function MyApp({ Component, pageProps }) {
  return ;
}

export default MyApp;

Running Your Application

Now, start your Next.js development server:

npm run dev

Open your browser and navigate to `http://localhost:3000`. You should see your event calendar, displaying the hardcoded events. You can interact with the calendar, navigate through months, and see the event details.

Adding Event Creation and Management

Let’s enhance our calendar by adding the ability to create, edit, and delete events. This will involve adding a form to input event details and updating our API route to handle these operations.

Creating the Event Form Component

First, create a new component for the event form. Create a file `components/EventForm.js`:


// components/EventForm.js
import React, { useState } from 'react';

const EventForm = ({ onAddEvent, onEditEvent, eventToEdit, onClose }) => {
  const [title, setTitle] = useState(eventToEdit?.title || '');
  const [start, setStart] = useState(eventToEdit?.start ? new Date(eventToEdit.start).toISOString().slice(0, 16) : '');
  const [end, setEnd] = useState(eventToEdit?.end ? new Date(eventToEdit.end).toISOString().slice(0, 16) : '');

  const handleSubmit = async (e) => {
    e.preventDefault();
    const eventData = {
      title,
      start: new Date(start).toISOString(),
      end: new Date(end).toISOString(),
    };

    if (eventToEdit) {
      // Editing an event
      onEditEvent(eventToEdit.id, eventData);
    } else {
      // Adding a new event
      onAddEvent(eventData);
    }

    setTitle('');
    setStart('');
    setEnd('');
    onClose();
  };

  return (
    <div>
      <div>
        <button>×</button>
        <h2>{eventToEdit ? 'Edit Event' : 'Add Event'}</h2>
        
          <div>
            <label>Title:</label>
             setTitle(e.target.value)}
              required
            />
          </div>
          <div>
            <label>Start:</label>
             setStart(e.target.value)}
              required
            />
          </div>
          <div>
            <label>End:</label>
             setEnd(e.target.value)}
              required
            />
          </div>
          <button type="submit">{eventToEdit ? 'Update Event' : 'Add Event'}</button>
        
      </div>
    </div>
  );
};

export default EventForm;

This component provides a form to input event details. It takes `onAddEvent`, `onEditEvent`, `eventToEdit`, and `onClose` as props. It uses the `useState` hook to manage the form’s input values and handles form submission to add or edit events.

Let’s add some CSS for the form. Add the following to `styles/globals.css`:


.event-form-overlay {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.5);
  display: flex;
  justify-content: center;
  align-items: center;
  z-index: 1000;
}

.event-form {
  background-color: white;
  padding: 20px;
  border-radius: 8px;
  box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
  width: 80%;
  max-width: 500px;
  position: relative;
}

.event-form label {
  display: block;
  margin-bottom: 5px;
  font-weight: bold;
}

.event-form input {
  width: 100%;
  padding: 8px;
  margin-bottom: 15px;
  border: 1px solid #ccc;
  border-radius: 4px;
  box-sizing: border-box;
}

.event-form button {
  background-color: #007bff;
  color: white;
  padding: 10px 15px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-size: 16px;
}

.event-form button:hover {
  background-color: #0056b3;
}

.close-button {
  position: absolute;
  top: 10px;
  right: 10px;
  background: none;
  border: none;
  font-size: 20px;
  cursor: pointer;
}

Updating the Calendar Component to Handle Events

Modify the `components/Calendar.js` to include the event form and handle event creation, editing, and deletion. Here’s the updated code:


// components/Calendar.js
import React, { useState, useEffect } from 'react';
import { Calendar, momentLocalizer } from 'react-big-calendar';
import moment from 'moment';
import 'react-big-calendar/lib/css/react-big-calendar.css';
import EventForm from './EventForm';

const localizer = momentLocalizer(moment);

const MyCalendar = () => {
  const [events, setEvents] = useState([]);
  const [showForm, setShowForm] = useState(false);
  const [eventToEdit, setEventToEdit] = useState(null);

  useEffect(() => {
    const fetchEvents = async () => {
      try {
        const response = await fetch('/api/events');
        const data = await response.json();
        const formattedEvents = data.map(event => ({
          ...event,
          start: new Date(event.start),
          end: new Date(event.end),
        }));
        setEvents(formattedEvents);
      } catch (error) {
        console.error('Error fetching events:', error);
      }
    };

    fetchEvents();
  }, []);

  const handleAddEvent = async (newEvent) => {
    try {
      const response = await fetch('/api/events', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(newEvent),
      });

      if (response.ok) {
        const addedEvent = await response.json();
        setEvents(prevEvents => [...prevEvents, { ...addedEvent, start: new Date(addedEvent.start), end: new Date(addedEvent.end) }]);
        console.log('Event added successfully');
      } else {
        console.error('Failed to add event');
      }
    } catch (error) {
      console.error('Error adding event:', error);
    }
  };

  const handleEditEvent = async (eventId, updatedEvent) => {
    try {
      const response = await fetch(`/api/events/${eventId}`, {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(updatedEvent),
      });

      if (response.ok) {
        const updatedEventData = await response.json();
        setEvents(prevEvents =>
          prevEvents.map(event =>
            event.id === eventId
              ? { ...updatedEventData, start: new Date(updatedEventData.start), end: new Date(updatedEventData.end) } // Update the event
              : event
          )
        );
        console.log('Event updated successfully');
        setEventToEdit(null); // Close the form after updating
      } else {
        console.error('Failed to update event');
      }
    } catch (error) {
      console.error('Error updating event:', error);
    }
  };

  const handleDeleteEvent = async (eventId) => {
    try {
      const response = await fetch(`/api/events/${eventId}`, {
        method: 'DELETE',
      });

      if (response.ok) {
        setEvents(prevEvents => prevEvents.filter(event => event.id !== eventId));
        console.log('Event deleted successfully');
      } else {
        console.error('Failed to delete event');
      }
    } catch (error) {
      console.error('Error deleting event:', error);
    }
  };

  const handleSelectEvent = (event) => {
    setEventToEdit(event);
    setShowForm(true);
  };

  const handleSelectSlot = (slotInfo) => {
    setEventToEdit(null);
    const start = slotInfo.start;
    const end = slotInfo.end;
    const formattedStart = start.toISOString().slice(0, 16);
    const formattedEnd = end.toISOString().slice(0, 16);
    setEventToEdit({
      start: formattedStart,
      end: formattedEnd
    });
    setShowForm(true);
  };

  const handleCloseForm = () => {
    setShowForm(false);
    setEventToEdit(null);
  };

  return (
    <div>
      <button> {
        setEventToEdit(null);
        setShowForm(true);
      }}>
        Add Event
      </button>
      
      {showForm && (
        
      )}
    </div>
  );
};

export default MyCalendar;

Key changes:

  • State Variables: We added `showForm` (to control the visibility of the form) and `eventToEdit` (to store the event being edited).
  • handleAddEvent Function: This function is called when a new event is submitted through the form. It sends a POST request to `/api/events` to add the event. The API route is expected to return the newly created event with an `id`.
  • handleEditEvent Function: This function is called when an existing event is edited. It sends a PUT request to `/api/events/:id` to update the event.
  • handleDeleteEvent Function: This function is included to handle event deletion. It sends a DELETE request to `/api/events/:id`.
  • handleSelectEvent Function: This function is called when you click on an event in the calendar. It sets `eventToEdit` to the selected event and shows the form.
  • handleSelectSlot Function: This function is called when you click on a time slot in the calendar. It sets `eventToEdit` to a new event with the start and end times based on the selected slot and shows the form.
  • handleCloseForm Function: This function closes the form and resets `eventToEdit`.
  • Calendar Component Props: We’ve added `selectable` to the `Calendar` component, enabling the selection of time slots and events. We’ve also added `onSelectEvent` and `onSelectSlot` to handle event selection and slot selection, respectively.
  • EventForm Rendering: The `EventForm` component is conditionally rendered based on the `showForm` state.

Updating the API Route to Handle CRUD Operations

Modify the `pages/api/events.js` file to handle the POST, PUT, and DELETE requests. We’ll use a simple in-memory array to store events for this example. In a real-world application, you would use a database.


// pages/api/events.js
let events = [
  {
    id: 1,
    title: 'Meeting with John',
    start: '2024-01-20T10:00:00.000Z',
    end: '2024-01-20T11:00:00.000Z',
  },
  {
    id: 2,
    title: 'Project Deadline',
    start: '2024-01-22T14:00:00.000Z',
    end: '2024-01-22T16:00:00.000Z',
  },
];

export default async function handler(req, res) {
  if (req.method === 'GET') {
    res.status(200).json(events);
  } else if (req.method === 'POST') {
    const newEvent = { ...req.body, id: Date.now() }; // Assign a unique ID
    events.push(newEvent);
    res.status(201).json(newEvent);
  } else if (req.method === 'PUT') {
    const { id } = req.query;
    const updatedEvent = req.body;
    events = events.map(event => (event.id === parseInt(id) ? { ...updatedEvent, id: parseInt(id) } : event));
    res.status(200).json(updatedEvent);
  } else if (req.method === 'DELETE') {
    const { id } = req.query;
    events = events.filter(event => event.id !== parseInt(id));
    res.status(200).json({ message: 'Event deleted' });
  } else {
    res.status(405).json({ message: 'Method not allowed' });
  }
}

Key changes:

  • GET Request: The GET request remains the same, returning the list of events.
  • POST Request: This handles event creation. It parses the request body, assigns a unique ID to the new event, adds the event to the `events` array, and returns the newly created event with a 201 status code (Created).
  • PUT Request: This handles event updates. It retrieves the event ID from the query parameters, parses the request body, and updates the event in the `events` array.
  • DELETE Request: This handles event deletion. It retrieves the event ID from the query parameters and removes the event from the `events` array.
  • Error Handling: Includes a default `else` block to return a 405 (Method Not Allowed) status for any other HTTP methods.

Testing the Event Calendar

Now, restart your development server (if necessary) and test the application:

  1. Click the “Add Event” button to open the form.
  2. Fill in the event details (title, start, and end times).
  3. Click the “Add Event” button to save the event. The new event should appear in the calendar.
  4. Click an event in the calendar. This should open the edit form pre-filled with the event’s data.
  5. Edit the event details and click “Update Event”. The event in the calendar should be updated.
  6. Click an event to open the edit form, and there should be a delete button.

Common Mistakes and How to Fix Them

Here are some common mistakes developers make when building event calendars and how to fix them:

  • Incorrect Date/Time Formatting: Ensure that you are consistently using the correct date and time formats. `react-big-calendar` and the `Date` objects in JavaScript can be sensitive to formatting. Use `toISOString()` to format dates consistently and the `Date` object in JavaScript to create new dates.
  • Incorrect Timezone Handling: Be mindful of timezone differences, especially if your users are in different time zones. Consider using a library like `moment-timezone` to handle time zone conversions. Store all dates in UTC in your database and convert to the user’s timezone when displaying them.
  • API Route Errors: Double-check your API routes for any errors. Use console logs to debug and ensure that data is being sent and received correctly. Use the browser’s developer tools (Network tab) to inspect the API requests and responses.
  • State Management Issues: Carefully manage the state of your events. Incorrectly updating the state can lead to unexpected behavior. Use the correct state update functions (e.g., `setEvents`) to ensure that the calendar re-renders correctly. Make sure you’re not mutating the state directly; always create a new copy of the array or object when updating.
  • CSS Conflicts: If you’re having styling issues, make sure your CSS is correctly applied and that there are no conflicts with other CSS rules. Use browser developer tools to inspect the styles and identify any conflicts.
  • Missing Dependencies: Ensure that you’ve installed all the necessary dependencies (e.g., `react-big-calendar`, `date-fns`, and `moment`).
  • Incorrect Event Data Format: Ensure that the event data you are passing to the `react-big-calendar` component matches the expected format (i.e., with `start`, `end`, and `title` properties).

Key Takeaways

  • Next.js is a powerful framework for building interactive web applications. Its features like server-side rendering, API routes, and optimized performance make it an excellent choice for projects like an event calendar.
  • `react-big-calendar` is a versatile component for displaying and interacting with calendars. It provides a comprehensive set of features and is easy to integrate into your React applications.
  • API routes in Next.js simplify the process of creating backend endpoints. You can easily create routes to handle data fetching, event creation, editing, and deletion.
  • State management is critical for building dynamic applications. Properly managing state ensures that your application updates correctly and responds to user interactions.
  • Thorough testing and debugging are essential for identifying and fixing errors. Use console logs, browser developer tools, and careful code reviews to ensure your application works as expected.

FAQ

Here are some frequently asked questions about building an event calendar with Next.js:

  1. Can I use a different calendar library? Yes, you can. While `react-big-calendar` is used in this tutorial, you can use other libraries like `react-calendar` or build your own custom calendar component.
  2. How do I store events persistently? In the current example, the events are stored in-memory and are lost when the server restarts. To store events persistently, you’ll need to use a database (e.g., MongoDB, PostgreSQL, or Firebase) and connect your API routes to the database.
  3. How can I add user authentication? You can integrate user authentication using libraries like `next-auth` or by implementing your own authentication system. This will allow you to restrict access to the calendar and allow each user to manage their own events.
  4. How can I add recurring events? Adding recurring events involves more complex logic. You’ll need to implement a system to define the recurrence pattern (e.g., daily, weekly, monthly) and generate the event instances accordingly. Libraries like `rrule` can help with this.
  5. How can I deploy this application? You can deploy your Next.js application to platforms like Vercel (recommended), Netlify, or other hosting providers that support Node.js applications.

Creating an event calendar is a valuable project, but it also serves as a gateway to understanding more complex web development concepts. You can extend this project in numerous ways, such as adding user authentication, integrating with external APIs (like Google Calendar), implementing recurring events, and more. Experiment, iterate, and learn—that’s the essence of becoming a proficient developer. As you continue to build and refine your skills, you’ll find that the ability to create functional, user-friendly applications is not just a technical skill, but a powerful form of creative expression.