---
title: "React Agenda Load Public Google Calendar Events Example | Mobiscroll"
component: "Agenda"
framework: "React"
library_version: "6.2.0"
url: "https://demo.mobiscroll.com/react/agenda/load-events-from-google-calendar"
---

# Load Public Google Calendar Events - React Agenda demo

Load events from a public Google Calendar into the React agenda view using the integration plugin — connect with a Calendar ID and API key. For React and Next.JS.

- [Live demo](https://demo.mobiscroll.com/react/agenda/load-events-from-google-calendar)
- [View React JS code on GitHub](https://github.com/acidb/mobiscroll-demos-react/tree/main/src/demos/eventcalendar/agenda/load-events-from-google-calendar)
- [View React TS code on GitHub](https://github.com/acidb/mobiscroll-demos-react-ts/tree/main/src/demos/eventcalendar/agenda/load-events-from-google-calendar)
- This demo in other frameworks: [JavaScript](https://demo.mobiscroll.com/javascript/agenda/load-events-from-google-calendar.md), [jQuery](https://demo.mobiscroll.com/jquery/agenda/load-events-from-google-calendar.md), [Angular](https://demo.mobiscroll.com/angular/agenda/load-events-from-google-calendar.md), [Vue](https://demo.mobiscroll.com/vue/agenda/load-events-from-google-calendar.md)

## Description

Event data can be loaded from remote sources, like public google calendars. Through the [**integration plugin**](https://mobiscroll.com/docs/react/eventcalendar/calendar-integrations) you can easily show events that are available in a public calendar. The integration plugin needs to be added to the download package or installed separately from a dedicated NPM package.

You will just need the `CALENDAR_ID` and an `apiKey` from google and you should be ready to roll.

Public calendars are read-only. If you are interested in syncing private google calendars with read/write/delete access, [check out this example](https://demo.mobiscroll.com/react/agenda/sync-events-google-calendar).

This example uses the [**integration plugin**](https://mobiscroll.com/docs/react/eventcalendar/calendar-integrations) to connect to and load events from Google Calendar and it is not available in the trial. Give the live demo a try and see how it can be configured.

## What this demo shows

- Shows a weekly agenda view with events listed and grouped by date.
- **Custom header** The current month and year are shown on the left side of the header.
- **View switching** A segmented control in the center lets users switch between `Month`, `Week`, `Day`, and `Agenda` views, with `Agenda` selected by default.
- **Secondary navigation** Blue previous and next arrow buttons are shown on the right side of the header with a `Today` button between them.
- **Week view** A week calendar is displayed at the top of the view.
- **Week navigation** The week strip can be changed by clicking and dragging left or right.
- **Day cell states** Hovering a day highlights the day number with a gray background. Selecting a day highlights the day number with a blue background. The current date is highlighted by default.
- **Event markers** Days with events show a small dot marker inside the day cell.
- **Agenda list** The area below the calendar lists events for the selected day.
- **Date selection** Selecting a day in the week calendar updates the agenda to show events for that date.
- **Events** Events appear as agenda cards with a colored strip on the left and the event title next to it. Start and end times are stacked on the right side of timed events. All-day events show the all-day label on the right.
- **Event interaction** Hovering an event highlights it.
- **Event selection** Clicking an event selects and highlights it.
- **Weekly scheduler view** Selecting the `Week` option from the segmented control switches the layout to a weekly scheduler view. This shows the days of the week at the top, an all-day section for all-day events, and a 24-hour time grid for each day of the week for timed events.
- **Daily scheduler view** Selecting the `Day` option from the segmented control switches the layout to a daily scheduler view. This shows the days of the week at the top, an all-day section for all-day events, and a 24-hour time grid for one day for timed events.
- **Scheduler view** The current time is marked with a blue line in the time grid.
- **Scheduler view** The time grid can be scrolled vertically with the mouse wheel while the header stays sticky.
- **Monthly calendar view** Selecting the `Month` option from the segmented control switches the layout to a monthly calendar view. Days with events display event labels inside the month grid cells.

## Best for

- **Google Calendar overlays** Showing events from public Google Calendars inside an agenda view.
- **Read-only event publishing** Sharing team, organization, or community calendars where visitors need to browse events but not edit them.

## API used in this demo

- [`theme`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-theme) - option
- [`themeVariant`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-themeVariant) - option
- [`onInit`](https://mobiscroll.com/docs/react/eventcalendar/api#event-onInit) - event
- [`data`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-data) - option
- [`exclusiveEndDates`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-exclusiveEndDates) - option
- [`renderHeader`](https://mobiscroll.com/docs/react/eventcalendar/api#renderer-renderHeader) - renderer
- [`view`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-view) - option
- [`onPageLoading`](https://mobiscroll.com/docs/react/eventcalendar/api#event-onPageLoading) - event
- [`onClose`](https://mobiscroll.com/docs/react/notifications/toast#event-onClose) - event

## Code

### JSX

```jsx
import { googleCalendarSync } from '@mobiscroll/calendar-integration';
import {
  CalendarNav,
  CalendarNext,
  CalendarPrev,
  CalendarToday,
  Eventcalendar,
  Segmented,
  SegmentedGroup,
  setOptions,
  Toast,
} from '@mobiscroll/react';
import { useCallback, useEffect, useRef, useState } from 'react';

setOptions({
  theme: 'ios',
  themeVariant: 'light'
});

const CALENDAR_ID = 'theacidmedia.net_8l6v679q5j2f7q8lpmcjr4mm3k@group.calendar.google.com';

function App() {
  const [currentView, setCurrentView] = useState('agenda');
  const [isLoading, setLoading] = useState(false);
  const [isToastOpen, setToastOpen] = useState(false);
  const [myEvents, setEvents] = useState([]);
  const [myView, setView] = useState({
    calendar: { type: 'week' },
    agenda: { type: 'week' },
  });
  const [toastMessage, setToastMessage] = useState('');

  const firstDay = useRef();
  const lastDay = useRef();

  const handleError = useCallback((resp) => {
    setToastMessage(resp.error ? resp.error : resp.result.error.message);
    setToastOpen(true);
  }, []);

  const loadEvents = useCallback(() => {
    setTimeout(() => {
      setLoading(true);
    });
    googleCalendarSync
      .getEvents(CALENDAR_ID, firstDay.current, lastDay.current)
      .then((resp) => {
        setEvents(resp);
        setLoading(false);
      })
      .catch(handleError);
  }, [handleError]);

  const changeView = useCallback((event) => {
    let view;

    switch (event.target.value) {
      case 'month':
        view = {
          calendar: { type: 'month' },
        };
        break;
      case 'week':
        view = {
          scheduler: { type: 'week' },
        };
        break;
      case 'day':
        view = {
          scheduler: { type: 'day' },
        };
        break;
      case 'agenda':
      default:
        view = {
          calendar: { type: 'week' },
          agenda: { type: 'week' },
        };
        break;
    }

    setCurrentView(event.target.value);
    setView(view);
  }, []);

  const handlePageLoading = useCallback(
    (event) => {
      const start = event.viewStart;
      const end = event.viewEnd;

      // Calculate dates
      // (pre-load events for previous and next pages as well)
      if (currentView === 'month') {
        firstDay.current = start;
        lastDay.current = end;
      } else {
        firstDay.current = new Date(start.getFullYear(), start.getMonth(), start.getDate() - 7);
        lastDay.current = new Date(end.getFullYear(), end.getMonth(), end.getDate() + 7);
      }

      loadEvents();
    },
    [loadEvents, currentView],
  );

  const handleCloseToast = useCallback(() => {
    setToastOpen(false);
  }, []);

  const customHeader = useCallback(
    () => (
      <>
        <CalendarNav className="mds-google-cal-nav" />
        <div className={'mds-loader' + (isLoading ? ' mds-loader-visible' : '')}></div>
        <div className="mds-google-cal-switch mbsc-flex-1-0">
          <SegmentedGroup value={currentView} onChange={changeView}>
            <Segmented value="month">Month</Segmented>
            <Segmented value="week">Week</Segmented>
            <Segmented value="day">Day</Segmented>
            <Segmented value="agenda">Agenda</Segmented>
          </SegmentedGroup>
        </div>
        <CalendarPrev className="mds-google-cal-prev" />
        <CalendarToday className="mds-google-cal-today" />
        <CalendarNext className="mds-google-cal-next" />
      </>
    ),
    [changeView, currentView, isLoading],
  );

  useEffect(() => {
    googleCalendarSync.init({
      apiKey: '<YOUR_GOOGLE_API_KEY>',
      onInit: loadEvents,
    });
  }, [loadEvents]);

  return (
    <>
      <Eventcalendar data={myEvents} exclusiveEndDates={true} renderHeader={customHeader} view={myView} onPageLoading={handlePageLoading} />
      <Toast isOpen={isToastOpen} message={toastMessage} onClose={handleCloseToast} />
    </>
  );
}

export default App;
```

### TSX

```tsx
import { googleCalendarSync } from '@mobiscroll/calendar-integration';
import {
  CalendarNav,
  CalendarNext,
  CalendarPrev,
  CalendarToday,
  Eventcalendar,
  MbscCalendarEvent,
  MbscEventcalendarView,
  MbscPageLoadingEvent,
  Segmented,
  SegmentedGroup,
  setOptions,
  Toast,
} from '@mobiscroll/react';
import { ChangeEvent, FC, useCallback, useEffect, useRef, useState } from 'react';

setOptions({
  theme: 'ios',
  themeVariant: 'light'
});

const CALENDAR_ID = 'theacidmedia.net_8l6v679q5j2f7q8lpmcjr4mm3k@group.calendar.google.com';

const App: FC = () => {
  const [currentView, setCurrentView] = useState<string>('agenda');
  const [isLoading, setLoading] = useState<boolean>(false);
  const [isToastOpen, setToastOpen] = useState<boolean>(false);
  const [myEvents, setEvents] = useState<MbscCalendarEvent[]>([]);
  const [myView, setMyView] = useState<MbscEventcalendarView>({
    calendar: { type: 'week' },
    agenda: { type: 'week' },
  });
  const [toastMessage, setToastMessage] = useState<string>('');

  const firstDay = useRef<Date>(null);
  const lastDay = useRef<Date>(null);

  const handleError = useCallback((resp: { error: string; result: { error: { message: string } } }) => {
    setToastMessage(resp.error ? resp.error : resp.result.error.message);
    setToastOpen(true);
  }, []);

  const loadEvents = useCallback(() => {
    setTimeout(() => {
      setLoading(true);
    });
    googleCalendarSync
      .getEvents(CALENDAR_ID, firstDay.current!, lastDay.current!)
      .then((resp: MbscCalendarEvent[]) => {
        setLoading(false);
        setEvents(resp);
      })
      .catch(handleError);
  }, [firstDay, lastDay, handleError]);

  const changeView = (event: ChangeEvent<HTMLInputElement>) => {
    let view: MbscEventcalendarView;

    switch (event.target.value) {
      case 'month':
        view = {
          calendar: { type: 'month' },
        };
        break;
      case 'week':
        view = {
          scheduler: { type: 'week' },
        };
        break;
      case 'day':
        view = {
          scheduler: { type: 'day' },
        };
        break;
      case 'agenda':
      default:
        view = {
          calendar: { type: 'week' },
          agenda: { type: 'week' },
        };
        break;
    }

    setCurrentView(event.target.value);
    setMyView(view);
  };

  const handlePageLoading = useCallback(
    (event: MbscPageLoadingEvent) => {
      const start = event.viewStart;
      const end = event.viewEnd;

      // Calculate dates
      // (pre-load events for previous and next pages as well)
      if (currentView === 'month') {
        firstDay.current = start;
        lastDay.current = end;
      } else {
        firstDay.current = new Date(start!.getFullYear(), start!.getMonth(), start!.getDate() - 7);
        lastDay.current = new Date(end!.getFullYear(), end!.getMonth(), end!.getDate() + 7);
      }

      loadEvents();
    },
    [loadEvents, currentView],
  );

  const handleCloseToast = useCallback(() => {
    setToastOpen(false);
  }, []);

  const customHeader = () => (
    <>
      <CalendarNav className="mds-google-cal-nav" />
      <div className={'mds-loader' + (isLoading ? ' mds-loader-visible' : '')}></div>
      <div className="mds-google-cal-switch mbsc-flex-1-0">
        <SegmentedGroup value={currentView} onChange={changeView}>
          <Segmented value="month">Month</Segmented>
          <Segmented value="week">Week</Segmented>
          <Segmented value="day">Day</Segmented>
          <Segmented value="agenda">Agenda</Segmented>
        </SegmentedGroup>
      </div>
      <CalendarPrev className="mds-google-cal-prev" />
      <CalendarToday className="mds-google-cal-today" />
      <CalendarNext className="mds-google-cal-next" />
    </>
  );

  useEffect(() => {
    googleCalendarSync.init({
      apiKey: '<YOUR_GOOGLE_API_KEY>',
      onInit: loadEvents,
    });
  }, [loadEvents]);

  return (
    <>
      <Eventcalendar data={myEvents} exclusiveEndDates={true} renderHeader={customHeader} view={myView} onPageLoading={handlePageLoading} />
      <Toast isOpen={isToastOpen} message={toastMessage} onClose={handleCloseToast} />
    </>
  );
};
export default App;
```

### CSS

```css
.mds-google-cal-switch .mbsc-segmented {
  max-width: 300px;
  margin: 0 auto;
}

.mds-google-cal-nav {
  width: 200px;
}

.mds-loader {
  width: 32px;
  height: 32px;
  border: 4px solid #8c8c8c;
  border-bottom-color: transparent;
  border-radius: 50%;
  display: inline-block;
  box-sizing: border-box;
  animation: mds-loader-rotation 1s linear infinite;
  visibility: hidden;
}

.mds-loader-visible {
  visibility: visible;
}

@keyframes mds-loader-rotation {
  0% {
    transform: rotate(0deg);
  }
  100% {
    transform: rotate(360deg);
  }
}

/* Material header order */

.mbsc-material.mds-google-cal-prev {
  order: 1;
}

.mbsc-material.mds-google-cal-next {
  order: 2;
}

.mbsc-material.mds-google-cal-nav {
  order: 3;
}

.mbsc-material .mds-google-cal-switch {
  order: 4;
}

.mbsc-material .mds-google-cal-today {
  order: 5;
}

/* Windows header order */

.mbsc-windows.mds-google-cal-nav {
  order: 1;
}

.mbsc-windows.mds-google-cal-prev {
  order: 2;
}

.mbsc-windows.mds-google-cal-next {
  order: 3;
}

.mbsc-windows .mds-google-cal-switch {
  order: 4;
}

.mbsc-windows .mds-google-cal-today {
  order: 5;
}
```

## Implementation notes

- Install the `@mobiscroll/calendar-integration` package. Call `googleCalendarSync.init({ apiKey: '<YOUR_GOOGLE_API_KEY>', onInit: loadEvents })` on mount to authenticate. Provide a `CALENDAR_ID` (the public Google Calendar ID).
- Set `exclusiveEndDates: true` on the Eventcalendar - Google Calendar uses exclusive end dates.
- Start with `view: { calendar: { type: 'week' }, agenda: { type: 'week' } }`. The view is switchable via a `SegmentedGroup` (Month / Week / Day / Agenda) in the custom header. On segment change, update the view: month → `{ calendar: { type: 'month' } }`, week → `{ scheduler: { type: 'week' } }`, day → `{ scheduler: { type: 'day' } }`, agenda → `{ calendar: { type: 'week' }, agenda: { type: 'week' } }`.
- Use `onPageLoading` to load events on every page change. Extract `args.viewStart` and `args.viewEnd` to determine the range. For non-month views, extend the range ±7 days to pre-load the previous and next pages. Call `googleCalendarSync.getEvents(CALENDAR_ID, firstDay, lastDay)` - it returns a Promise. On resolve, update the events array; on reject, show a Toast with the error message from `resp.error` or `resp.result.error.message`.
- Build a custom header using `renderHeader` (Angular: `headerTemplate`, Vue: `header` slot) containing: `CalendarNav`, a CSS loading spinner (toggle visibility while the Promise is pending), a `SegmentedGroup` for view switching, `CalendarPrev`, `CalendarToday`, `CalendarNext`.

## Run it in your project

Running a demo locally requires a Mobiscroll trial or a license; setup is per framework at https://mobiscroll.com/docs/react/getting-started/installation

## Related demos

- Previous: [Outlook Calendar Sync](https://demo.mobiscroll.com/react/agenda/sync-events-outlook-calendar.md) - Use the React event calendar integration plugin to authenticate users, load their Outlook calendars, and display events in an agenda view.
