---
title: "React Agenda Google Calendar Sync Example | Mobiscroll"
component: "Agenda"
framework: "React"
library_version: "6.2.0"
url: "https://demo.mobiscroll.com/react/agenda/sync-events-google-calendar"
---

# Google Calendar Sync - React Agenda demo

Load and sync Google Calendar events in a React agenda view — authenticate users, toggle calendars on/off, and create events using the plugin. For React and Next.JS.

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

## Description

The event calendar comes with third party calendar integration support through the [**integration plugin**](https://mobiscroll.com/docs/react/eventcalendar/calendar-integrations). This needs to be added to the download package or installed separately from a dedicated NPM package.

It includes everything you need to authenticate your users, get their Google calendars and provides functions for CRUD operations. It takes care of loading the events and converts them to the required format so that they can be displayed on the Mobiscroll calendar separately or mixed with other calendars and events.

Customizing the interaction, event workflows, whether the events are read-only is up to you. In this live demo, newly created events will land in the `primary calendar` of the authenticated user.

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 monthly agenda view with events listed and grouped by date.
- **Header navigation** The month and year label in the top left opens date navigation. The previous and next arrows and the Today button on the right move between months and jump back to the current date.
- **Signed-out state** No events are loaded by default. The `Sync My Google Calendars` button in the header prompts the user to sign in to Google Calendar.
- **Authentication flow** Clicking the Google sign-in button opens a new browser tab where the user can authenticate with their Google account.
- **Signed-in state** After sign-in, the header button changes to `My Calendars`.
- **Calendar list** The `My Calendars` popup shows multiple calendar switches, such as the user's personal calendar and country-specific holiday calendars.
- **Sign-out** The bottom of the popup includes a `Log out of my account` button for disconnecting the Google Calendar account.
- **Agenda list** The area below the header lists events for the selected month. Events in the agenda are grouped by date.
- **Sticky day headers** As the agenda scrolls vertically, each day stays visible with a sticky date header while its events appear underneath.
- **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.

## Best for

- **Personal calendar scheduling** Building agenda views where users can browse and manage events from their own Google Calendar.
- **Calendar visibility controls** Letting users toggle individual Google calendars on or off inside the same agenda view.
- **Readonly calendar handling** Displaying calendars without write access while preventing edits to those calendar rows.

## 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
- [`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
- [`selectedDate`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-selectedDate) - option
- [`view`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-view) - option
- [`onPageLoading`](https://mobiscroll.com/docs/react/eventcalendar/api#event-onPageLoading) - event
- [`onSelectedDateChange`](https://mobiscroll.com/docs/react/eventcalendar/api#event-onSelectedDateChange) - event
- [`anchor`](https://mobiscroll.com/docs/react/popup/api#opt-anchor) - option
- [`display`](https://mobiscroll.com/docs/react/popup/api#opt-display) - option
- [`isOpen`](https://mobiscroll.com/docs/react/popup/api#opt-isOpen) - option
- [`scrollLock`](https://mobiscroll.com/docs/react/popup/api#opt-scrollLock) - option
- [`showOverlay`](https://mobiscroll.com/docs/react/popup/api#opt-showOverlay) - option
- [`touchUi`](https://mobiscroll.com/docs/react/popup/api#opt-touchUi) - option
- [`width`](https://mobiscroll.com/docs/react/popup/api#opt-width) - option
- [`onClose`](https://mobiscroll.com/docs/react/popup/api#event-onClose) - event
- [`onClose`](https://mobiscroll.com/docs/react/notifications/toast#event-onClose) - event

## Code

### JSX

```jsx
import { googleCalendarSync } from '@mobiscroll/calendar-integration';
import {
  Button,
  CalendarNav,
  CalendarNext,
  CalendarPrev,
  Eventcalendar,
  Popup,
  setOptions,
  Switch,
  Toast,
} from '@mobiscroll/react';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';

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

function App() {
  const [calendarData, setCalendarData] = useState([]);
  const [calendarIds, setCalendarIds] = useState([]);
  const [isLoggedIn, setIsLoggedIn] = useState(false);
  const [isLoading, setLoading] = useState(false);
  const [isPopupOpen, setPopupOpen] = useState(false);
  const [isToastOpen, setToastOpen] = useState(false);
  const [myAnchor, setAnchor] = useState();
  const [myCalendars, setCalendars] = useState([]);
  const [myEvents, setEvents] = useState([]);
  const [mySelectedDate, setSelectedDate] = useState(new Date());
  const [toastMessage, setToastMessage] = useState('');

  const buttonRef = useRef();
  const startDate = useRef();
  const endDate = useRef();
  const timer = useRef();

  const myView = useMemo(() => ({ agenda: { type: 'month' } }), []);

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

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

  const handlePopupClose = useCallback(() => {
    setPopupOpen(false);
  }, []);

  const handleSelectedDateChange = useCallback((args) => {
    setSelectedDate(args.date);
  }, []);

  const handlePageLoading = useCallback(
    (args) => {
      startDate.current = args.viewStart;
      endDate.current = args.viewEnd;
      clearTimeout(timer.current);
      timer.current = setTimeout(() => {
        if (googleCalendarSync.isSignedIn()) {
          setLoading(true);
          googleCalendarSync
            .getEvents(calendarIds, startDate.current, endDate.current)
            .then((resp) => {
              setEvents(resp);
              setLoading(false);
            })
            .catch(handleError);
        }
      }, 200);
    },
    [calendarIds, handleError],
  );

  const toggleCalendar = useCallback(
    (ev) => {
      const checked = ev.target.checked;
      const calendarId = ev.target.value;
      const updatedCalendarData = { ...calendarData, [calendarId]: { ...calendarData[calendarId], checked } };
      const newCalendarIds = checked ? [...calendarIds, calendarId] : calendarIds.filter((id) => id !== calendarId);
      setCalendarData(updatedCalendarData);
      setCalendarIds(newCalendarIds);
      if (newCalendarIds.length === 0) {
        setEvents([]);
        return;
      }
      setLoading(true);
      googleCalendarSync
        .getEvents(newCalendarIds, startDate.current, endDate.current)
        .then((events) => {
          setLoading(false);
          setEvents(events);
        })
        .catch(handleError);
    },
    [calendarData, calendarIds, handleError],
  );

  const openPopup = useCallback(() => {
    setAnchor(buttonRef.current.nativeElement);
    setPopupOpen(true);
  }, []);

  const navigate = useCallback(() => {
    setSelectedDate(new Date());
  }, []);

  const signIn = useCallback(() => {
    googleCalendarSync.signIn().catch(handleError);
  }, [handleError]);

  const signOut = useCallback(() => {
    googleCalendarSync.signOut().catch(handleError);
  }, [handleError]);

  const customHeader = useCallback(
    () => (
      <>
        <CalendarNav />
        <div className={'mds-loader' + (isLoading ? ' mds-loader-visible' : '')}></div>
        <div className="mbsc-flex mbsc-flex-1-0 mbsc-justify-content-end">
          {isLoggedIn ? (
            <Button ref={buttonRef} onClick={openPopup}>
              My Calendars
            </Button>
          ) : (
            <Button onClick={signIn}>Sync my Google calendars</Button>
          )}
          <Button onClick={navigate}>Today</Button>
        </div>
        <CalendarPrev />
        <CalendarNext />
      </>
    ),
    [isLoading, isLoggedIn, navigate, openPopup, signIn],
  );

  useEffect(() => {
    const onSignedIn = () => {
      setIsLoggedIn(true);
      googleCalendarSync
        .getCalendars()
        .then((calendars) => {
          const newCalendarIds = [];
          const newCalendarData = {};

          calendars.sort((c) => (c.primary ? -1 : 1));

          for (const c of calendars) {
            newCalendarIds.push(c.id);
            newCalendarData[c.id] = { checked: true };
          }

          setCalendarIds(newCalendarIds);
          setCalendarData(newCalendarData);
          setCalendars(calendars);
          setLoading(true);

          return googleCalendarSync.getEvents(newCalendarIds, startDate.current, endDate.current);
        })
        .then((events) => {
          setEvents(events);
          setLoading(false);
        })
        .catch(handleError);
    };

    const onSignedOut = () => {
      setCalendars([]);
      setCalendarIds([]);
      setCalendarData({});
      setIsLoggedIn(false);
      setEvents([]);
      setPopupOpen(false);
    };

    // Init Google client
    googleCalendarSync.init({
      apiKey: '<YOUR_GOOGLE_API_KEY>',
      clientId: '<YOUR_GOOGLE_CLIENT_ID>',
      onSignedIn: onSignedIn,
      onSignedOut: onSignedOut,
    });
  }, [handleError]);

  return (
    <>
      <Eventcalendar
        data={myEvents}
        exclusiveEndDates={true}
        renderHeader={customHeader}
        selectedDate={mySelectedDate}
        view={myView}
        onPageLoading={handlePageLoading}
        onSelectedDateChange={handleSelectedDateChange}
      />
      <Popup
        anchor={myAnchor}
        contentPadding={false}
        display="anchored"
        isOpen={isPopupOpen}
        scrollLock={false}
        showOverlay={false}
        touchUi={false}
        width={400}
        onClose={handlePopupClose}
      >
        <div className="mbsc-form-group-inset">
          <div className="mbsc-form-group-title">My Calendars</div>
          {myCalendars.map((cal) => (
            <Switch label={cal.summary} key={cal.id} value={cal.id} checked={calendarData[cal.id].checked} onChange={toggleCalendar} />
          ))}
        </div>
        <div className="mbsc-form-group-inset">
          <Button className="mbsc-button-block" onClick={signOut}>
            Sign out of my account
          </Button>
        </div>
      </Popup>
      <Toast isOpen={isToastOpen} message={toastMessage} onClose={handleToastClose} />
    </>
  );
}
export default App;
```

### TSX

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

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

const App: FC = () => {
  const [calendarData, setCalendarData] = useState<{ [key: string]: { checked: boolean } }>({});
  const [calendarIds, setCalendarIds] = useState<string[]>([]);
  const [isLoggedIn, setIsLoggedIn] = useState<boolean>(false);
  const [isLoading, setLoading] = useState<boolean>(false);
  const [isPopupOpen, setPopupOpen] = useState<boolean>(false);
  const [isToastOpen, setToastOpen] = useState<boolean>(false);
  const [myAnchor, setAnchor] = useState<HTMLElement>();
  const [myCalendars, setCalendars] = useState<Array<{ summary: string; id: string }>>([]);
  const [myEvents, setEvents] = useState<MbscCalendarEvent[]>([]);
  const [mySelectedDate, setSelectedDate] = useState(new Date());
  const [toastMessage, setToastMessage] = useState<string>('');

  const buttonRef = useRef<Button>(null);
  const startDate = useRef<Date>(null);
  const endDate = useRef<Date>(null);
  const timer = useRef<ReturnType<typeof setTimeout>>(undefined);

  const view = useMemo<MbscEventcalendarView>(() => ({ agenda: { type: 'month' } }), []);

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

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

  const handlePopupClose = useCallback(() => {
    setPopupOpen(false);
  }, []);

  const handleSelectedDateChange = useCallback((event: MbscSelectedDateChangeEvent) => {
    setSelectedDate(new Date(event.date as string));
  }, []);

  const handlePageLoading = useCallback(
    (args: MbscPageLoadingEvent) => {
      clearTimeout(timer.current);
      startDate.current = args.viewStart;
      endDate.current = args.viewEnd;
      timer.current = setTimeout(() => {
        if (googleCalendarSync.isSignedIn()) {
          setLoading(true);
          googleCalendarSync
            .getEvents(calendarIds, startDate.current!, endDate.current!)
            .then((resp: MbscCalendarEvent[]) => {
              setEvents(resp);
              setLoading(false);
            })
            .catch(handleError);
        }
      }, 200);
    },
    [calendarIds, handleError],
  );

  const toggleCalendar = useCallback(
    (ev: ChangeEvent<HTMLInputElement>) => {
      const checked = ev.target.checked;
      const calendarId = ev.target.value;
      if (calendarData) {
        const updatedCalendarData = { ...calendarData, [calendarId]: { ...calendarData[calendarId], checked } };
        setCalendarData(updatedCalendarData);
      }
      const newCalendarIds = checked ? [...calendarIds, calendarId] : calendarIds.filter((id) => id !== calendarId);
      setCalendarIds(newCalendarIds);
      if (newCalendarIds.length === 0) {
        setEvents([]);
        return;
      }
      setLoading(true);
      googleCalendarSync
        .getEvents(newCalendarIds, startDate.current!, endDate.current!)
        .then((events: MbscCalendarEvent[]) => {
          setLoading(false);
          setEvents(events);
        })
        .catch(handleError);
    },
    [calendarData, calendarIds, handleError],
  );

  const openPopup = useCallback(() => {
    setAnchor(buttonRef.current!.nativeElement);
    setPopupOpen(true);
  }, []);

  const navigate = useCallback(() => {
    setSelectedDate(new Date());
  }, []);

  const signIn = useCallback(() => {
    googleCalendarSync.signIn().catch(handleError);
  }, [handleError]);

  const signOut = useCallback(() => {
    googleCalendarSync.signOut().catch(handleError);
  }, [handleError]);

  const customHeader = useCallback(
    () => (
      <>
        <CalendarNav />
        <div className={'mds-loader' + (isLoading ? ' mds-loader-visible' : '')}></div>
        <div className="mbsc-flex mbsc-flex-1-0 mbsc-justify-content-end">
          {isLoggedIn ? (
            <Button ref={buttonRef} onClick={openPopup}>
              My Calendars
            </Button>
          ) : (
            <Button onClick={signIn}>Sync my Google calendars</Button>
          )}
          <Button onClick={navigate}>Today</Button>
        </div>
        <CalendarPrev />
        <CalendarNext />
      </>
    ),
    [isLoading, isLoggedIn, navigate, openPopup, signIn],
  );

  useEffect(() => {
    const onSignedIn = () => {
      setIsLoggedIn(true);
      googleCalendarSync
        .getCalendars()
        .then((calendars: { summary: string; id: string; primary: boolean }[]) => {
          const newCalendarIds: string[] = [];
          const newCalendarData: { [key: string]: { checked: boolean } } = {};

          calendars.sort((c) => (c.primary ? -1 : 1));

          for (const c of calendars) {
            newCalendarIds.push(c.id);
            newCalendarData[c.id] = { checked: true };
          }

          setCalendarIds(newCalendarIds);
          setCalendarData(newCalendarData);
          setCalendars(calendars);
          setLoading(true);
          return googleCalendarSync.getEvents(newCalendarIds, startDate.current!, endDate.current!);
        })
        .then((events: MbscCalendarEvent[]) => {
          setEvents(events);
          setLoading(false);
        })
        .catch(handleError);
    };

    const onSignedOut = () => {
      setIsLoggedIn(false);
      setCalendars([]);
      setCalendarIds([]);
      setCalendarData({});
      setEvents([]);
      setPopupOpen(false);
    };

    // Init Google client
    googleCalendarSync.init({
      apiKey: '<YOUR_GOOGLE_API_KEY>',
      clientId: '<YOUR_GOOGLE_CLIENT_ID>',
      onSignedIn: onSignedIn,
      onSignedOut: onSignedOut,
    });
  }, [handleError]);

  return (
    <>
      <Eventcalendar
        data={myEvents}
        exclusiveEndDates={true}
        renderHeader={customHeader}
        selectedDate={mySelectedDate}
        view={view}
        onPageLoading={handlePageLoading}
        onSelectedDateChange={handleSelectedDateChange}
      ></Eventcalendar>
      <Popup
        anchor={myAnchor}
        contentPadding={false}
        display="anchored"
        isOpen={isPopupOpen}
        scrollLock={false}
        showOverlay={false}
        touchUi={false}
        width={400}
        onClose={handlePopupClose}
      >
        <div className="mbsc-form-group-inset">
          <div className="mbsc-form-group-title">My Calendars</div>
          {myCalendars.map((cal) => (
            <Switch label={cal.summary} key={cal.id} value={cal.id} checked={calendarData[cal.id].checked} onChange={toggleCalendar} />
          ))}
        </div>
        <div className="mbsc-form-group-inset">
          <Button className="mbsc-button-block" onClick={signOut}>
            Log out of my account
          </Button>
        </div>
      </Popup>
      <Toast isOpen={isToastOpen} message={toastMessage} onClose={handleToastClose} />
    </>
  );
};
export default App;
```

### CSS

```css
.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);
  }
}
```

## Implementation notes

- Install `@mobiscroll/calendar-integration`. At component init call `googleCalendarSync.init({ apiKey: '<YOUR_GOOGLE_API_KEY>', clientId: '<YOUR_GOOGLE_CLIENT_ID>', onSignedIn, onSignedOut })`. Use `view: { agenda: { type: 'month' } }` with `exclusiveEndDates: true`.
- Maintain a `calendarIds` array for the currently enabled calendars. In `onPageLoading` store `args.viewStart`/`args.viewEnd`, debounce 200ms, then if signed in call `googleCalendarSync.getEvents(calendarIds, startDate, endDate)` and pass the result to `data`. For the imperative API call `inst.setEvents()`.
- In `onSignedIn`: call `googleCalendarSync.getCalendars()` to get the user's calendar list; initialise `calendarIds` with all calendar IDs (all enabled by default); call `googleCalendarSync.getEvents()` for the current view range. In `onSignedOut`: clear calendars, IDs, and events.
- Build a custom header via `renderHeader` (Angular: `headerTemplate`, Vue: `header` slot) containing: `CalendarNav`, a CSS spinning loader (visible while loading), then a right-aligned flex block with either a "Sync my Google calendars" Button (before login, calls `googleCalendarSync.signIn()`) or a "My Calendars" Button (after login, opens the calendar-list Popup) plus a "Today" Button. End with `CalendarPrev` and `CalendarNext`. The "Today" button updates `selectedDate` to today; JS/jQuery: call `inst.navigate(new Date())`.
- Show the calendar list in a `Popup` anchored to the "My Calendars" button: `display: 'anchored'`, `contentPadding: false`, `scrollLock: false`, `showOverlay: false`, `touchUi: false`, `width: 400`. Inside, render each calendar as a `Switch` - toggling one adds or removes its ID from `calendarIds` and re-fetches events via `googleCalendarSync.getEvents()`. Include a "Sign out" Button that calls `googleCalendarSync.signOut()`. Show API errors in a `Toast`.

## 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

- Next: [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.
