Integrations

API Integration

API Integration Guide

Learn how to connect your FullEventCalendar to external data sources and APIs to manage event data.

Overview

FullEventCalendar is designed to work seamlessly with any API or data source. The component provides several handler props that you can use to integrate with your backend:

  • onEventAdd - Called when a new event is created
  • onEventUpdate - Called when an existing event is updated
  • onEventDelete - Called when an event is deleted
  • onDateRangeChange - Called when the visible date range changes

Each of these handlers is Promise-based, allowing you to make asynchronous API calls to your backend.

Basic Integration Pattern

Here's the basic pattern for integrating with an API:

import { EventCalendar } from '@/components/event-calendar'; import { useEventCalendar } from '@/components/event-calendar/hooks/useEventCalendar'; import { CalendarEventType } from '@/components/event-calendar/types'; const CalendarWithAPI = () => { const { events, isLoading, addEvent, updateEvent, deleteEvent, onViewOrDateChange } = useEventCalendar({ initialEvents: [], onEventAdd: async (event) => { // API call to create event const response = await fetch('/api/events', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(event) }); if (!response.ok) { throw new Error('Failed to create event'); } // Return the created event with an ID return await response.json(); }, onEventUpdate: async (event) => { // API call to update event const response = await fetch(`/api/events/${event.id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(event) }); if (!response.ok) { throw new Error('Failed to update event'); } return event; }, onEventDelete: async (eventId) => { // API call to delete event const response = await fetch(`/api/events/${eventId}`, { method: 'DELETE' }); if (!response.ok) { throw new Error('Failed to delete event'); } }, onDateRangeChange: async (startDate, endDate, signal) => { // API call to fetch events for date range const start = startDate.toISOString(); const end = endDate.toISOString(); const response = await fetch( `/api/events?start=${start}&end=${end}`, { signal } ); if (!response.ok) { throw new Error('Failed to fetch events'); } return await response.json(); } }); return ( <EventCalendar events={events} isLoading={isLoading} onEventAdd={addEvent} onEventUpdate={updateEvent} onEventDelete={deleteEvent} onDateRangeChange={onViewOrDateChange} /> ); };

Error Handling

The useEventCalendar hook includes built-in error handling. When an error occurs:

  1. The operation fails gracefully
  2. The calendar remains in a consistent state
  3. An error toast notification is shown to the user

You can also implement your own error handling:

const { events, isLoading, addEvent, updateEvent, deleteEvent, onViewOrDateChange } = useEventCalendar({ // ...other props onEventAdd: async (event) => { try { const response = await fetch('/api/events', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(event) }); if (!response.ok) { // You can throw a custom error with a user-friendly message throw new Error('Unable to save event. Please try again later.'); } return await response.json(); } catch (error) { // Log error for debugging console.error('Create event error:', error); // Rethrow the error for the hook to handle throw error; } }, // Similar error handling for other operations... });

Loading States

The calendar can show loading states when fetching events. The isLoading state is automatically managed by the useEventCalendar hook:

<EventCalendar events={events} isLoading={isLoading} // Automatically managed by the hook // ...other props />

When isLoading is true, the calendar displays skeleton loaders in place of events.

Pagination and Lazy Loading

For large datasets, you can implement pagination or lazy loading using the onDateRangeChange handler:

onDateRangeChange: async (startDate, endDate, signal) => { // Convert dates to ISO strings for API parameters const start = startDate.toISOString(); const end = endDate.toISOString(); // Optional: Add pagination parameters const limit = 100; const page = 1; const response = await fetch( `/api/events?start=${start}&end=${end}&limit=${limit}&page=${page}`, { signal } ); if (!response.ok) { throw new Error('Failed to fetch events'); } return await response.json(); }

The signal parameter is an AbortSignal that you can pass to your fetch request. This allows the calendar to cancel in-flight requests when the user navigates quickly between date ranges.

Real-time Updates

To support real-time updates (e.g., from WebSockets), you can update the events array directly:

import { useState, useEffect } from 'react'; import { EventCalendar } from '@/components/event-calendar'; import { CalendarEventType } from '@/components/event-calendar/types'; const RealtimeCalendar = () => { const [events, setEvents] = useState<CalendarEventType[]>([]); useEffect(() => { // Set up WebSocket connection const socket = new WebSocket('wss://your-api.com/events'); socket.onmessage = (event) => { const data = JSON.parse(event.data); // Handle different types of updates if (data.type === 'new_event') { setEvents(prevEvents => [...prevEvents, data.event]); } else if (data.type === 'update_event') { setEvents(prevEvents => prevEvents.map(event => event.id === data.event.id ? data.event : event ) ); } else if (data.type === 'delete_event') { setEvents(prevEvents => prevEvents.filter(event => event.id !== data.eventId) ); } }; return () => { socket.close(); }; }, []); // CRUD handlers that update both local state and backend const handleAddEvent = async (event: Omit<CalendarEventType, 'id'>) => { const response = await fetch('/api/events', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(event) }); if (!response.ok) { throw new Error('Failed to create event'); } const newEvent = await response.json(); // No need to update state here if WebSocket will push the update return newEvent; }; // Similar handlers for update and delete... return ( <EventCalendar events={events} onEventAdd={handleAddEvent} // ...other handlers /> ); };

Authentication and Authorization

When working with authenticated APIs:

onEventAdd: async (event) => { // Get authentication token (e.g., from cookie, localStorage, or state) const token = getAuthToken(); const response = await fetch('/api/events', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, body: JSON.stringify(event) }); if (response.status === 401) { // Handle authentication errors redirectToLogin(); throw new Error('Please log in to continue'); } if (!response.ok) { throw new Error('Failed to create event'); } return await response.json(); }

Custom Data Mapping

If your API uses a different data structure than what the calendar expects, you'll need to map between the two:

// Example API event structure interface ApiEvent { eventId: string; title: string; description: string; beginAt: string; // ISO date string finishAt: string; // ISO date string category: string; isRecurring: boolean; recurrenceType?: string; } // Mapping functions const mapToCalendarEvent = (apiEvent: ApiEvent): CalendarEventType => { const startDate = new Date(apiEvent.beginAt); const endDate = new Date(apiEvent.finishAt); return { id: apiEvent.eventId, title: apiEvent.title, description: apiEvent.description || undefined, startDate: new Date(startDate.toDateString()), endDate: new Date(endDate.toDateString()), startTime: startDate.toTimeString().substring(0, 5), endTime: endDate.toTimeString().substring(0, 5), color: mapCategoryToColor(apiEvent.category), isRepeating: apiEvent.isRecurring, isRepeatingInterval: apiEvent.recurrenceType as any }; }; const mapToApiEvent = (calendarEvent: CalendarEventType): Omit<ApiEvent, 'eventId'> => { const startDateTime = new Date(`${calendarEvent.startDate.toDateString()} ${calendarEvent.startTime}`); const endDateTime = new Date(`${calendarEvent.endDate.toDateString()} ${calendarEvent.endTime}`); return { title: calendarEvent.title, description: calendarEvent.description || '', beginAt: startDateTime.toISOString(), finishAt: endDateTime.toISOString(), category: mapColorToCategory(calendarEvent.color), isRecurring: calendarEvent.isRepeating, recurrenceType: calendarEvent.isRepeatingInterval }; }; // Usage in API calls onEventAdd: async (event) => { const apiEvent = mapToApiEvent(event); const response = await fetch('/api/events', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(apiEvent) }); if (!response.ok) { throw new Error('Failed to create event'); } const createdApiEvent = await response.json(); return mapToCalendarEvent(createdApiEvent); }

Backend Implementation Examples

Next.js API Route

// /pages/api/events/index.ts import type { NextApiRequest, NextApiResponse } from 'next'; import { prisma } from '@/lib/prisma'; export default async function handler( req: NextApiRequest, res: NextApiResponse ) { if (req.method === 'GET') { const { start, end } = req.query; if (!start || !end) { return res.status(400).json({ error: 'Start and end dates are required' }); } try { const events = await prisma.event.findMany({ where: { OR: [ // Events that start within the range { startTime: { gte: new Date(start as string), lte: new Date(end as string) } }, // Events that end within the range { endTime: { gte: new Date(start as string), lte: new Date(end as string) } }, // Events that span the entire range { AND: [ { startTime: { lte: new Date(start as string) } }, { endTime: { gte: new Date(end as string) } } ] } ] } }); // Map database events to calendar events const calendarEvents = events.map(dbEventToCalendarEvent); return res.status(200).json(calendarEvents); } catch (error) { console.error('Error fetching events:', error); return res.status(500).json({ error: 'Failed to fetch events' }); } } else if (req.method === 'POST') { try { const eventData = req.body; // Convert calendar event to database model const dbEvent = calendarEventToDbEvent(eventData); const createdEvent = await prisma.event.create({ data: dbEvent }); // Convert back to calendar event format const calendarEvent = dbEventToCalendarEvent(createdEvent); return res.status(201).json(calendarEvent); } catch (error) { console.error('Error creating event:', error); return res.status(500).json({ error: 'Failed to create event' }); } } else { return res.status(405).json({ error: 'Method not allowed' }); } }

Express API

// events.js const express = require('express'); const router = express.Router(); const Event = require('../models/Event'); // Get events within date range router.get('/', async (req, res) => { const { start, end } = req.query; if (!start || !end) { return res.status(400).json({ error: 'Start and end dates are required' }); } try { const events = await Event.find({ $or: [ // Events that start within the range { startTime: { $gte: new Date(start), $lte: new Date(end) } }, // Events that end within the range { endTime: { $gte: new Date(start), $lte: new Date(end) } }, // Events that span the entire range { $and: [ { startTime: { $lte: new Date(start) } }, { endTime: { $gte: new Date(end) } } ] } ] }); // Map database events to calendar events const calendarEvents = events.map(dbEventToCalendarEvent); res.json(calendarEvents); } catch (error) { console.error('Error fetching events:', error); res.status(500).json({ error: 'Failed to fetch events' }); } }); // Create new event router.post('/', async (req, res) => { try { const eventData = req.body; // Convert calendar event to database model const dbEvent = calendarEventToDbEvent(eventData); const createdEvent = await Event.create(dbEvent); // Convert back to calendar event format const calendarEvent = dbEventToCalendarEvent(createdEvent); res.status(201).json(calendarEvent); } catch (error) { console.error('Error creating event:', error); res.status(500).json({ error: 'Failed to create event' }); } }); module.exports = router;

Testing API Integration

When testing your API integration:

  1. Test all CRUD operations (Create, Read, Update, Delete)
  2. Test with various date ranges
  3. Test error handling scenarios
  4. Test performance with large datasets
  5. Test cancellation of in-flight requests

Conclusion

FullEventCalendar's API integration capabilities make it easy to connect to any backend service. By implementing the appropriate handler functions and data mapping, you can create a seamless experience that keeps your calendar in sync with your backend data.