In this article you will learn how to use Fullcalendar (link here) in React and get the data from an API, paginated by the view (month, week, day) you’re looking at. My company often use RTK query for data fetching and caching, which is part of the Redux toolkit, so that’s what we will be using in this tutorial. Of course you can use any other fetching / data retrieving library.
Lets get into it.
Install the required packages
yarn add @fullcalendar/react
yarn add @fullcalendar/core
yarn add @fullcalendar/daygrid (this one is optional, this examples uses the daygridMonth view).
Setup a RTK Query slice
Our slice looks like this:
getGraphData: builder.query<GraphOutput, GetGraphInput>({
query: (params) => ({
url: "/v1/Data/GraphData",
method: "GET",
params: params,
}),
Add the lazy hook (useLazyGetGraphDataQuery).
And the interfaces Graphoutput and GetGraphInput look like this:
export interface GetGraphInput {
fromDate?: string;
tillDate?: string;
}
export interface GraphOutput {
result: {
title: string;
start: string;
end: string;
}[];
}
Add logic for the initial calendar load and when switching calendars
Create a new component and add the following ref, state, method and useEffect:
const calendarRef = useRef<FullCalendar>(null); // Create a ref for accessing the FullCalendar instance
const [graphData, setGraphData] = useState<GraphOutput["result"]>();
const [getGraphData] = useLazyGetGraphDataQuery();
const handleDatesSet = async (arg: DatesSetArg) => {
await getGraphData({
fromDate: dayjs(arg.start).format("YYYY-MM-DD"),
tillDate: dayjs(arg.end).format("YYYY-MM-DD"),
})
.unwrap()
.then((result) => {
setGraphData(result);
if (calendarRef.current) {
calendarRef.current.getApi().updateSize();
}
});
};
useEffect(() => {
const calendarApi = calendarRef.current?.getApi(); // Get the FullCalendar API from the ref
if (calendarApi) {
calendarApi.updateSize();
}
if (calendarApi) {
calendarApi.on("datesSet", handleDatesSet); // Attach the datesSet event listener
}
const date = new Date();
handleDatesSet({
end: new Date(date.getFullYear(), date.getMonth() + 1, 0),
start: new Date(date.getFullYear(), date.getMonth(), 1),
endStr: "",
startStr: "",
timeZone: "local",
view: {} as any,
});
return () => {
if (calendarApi) {
calendarApi.off("datesSet", handleDatesSet);
}
};
}, []);
The snippet above does a couple of things. It creates a ref to the fullcalendar component that we will render below in a minute. It defined a state that is going to hold the graphData that we retrieve from our API.
It also contains a method ‘handleDatesSet’ that is responsible for retrieving new data whenever we switch day/week/month/view.
The useEffect handles getting the initial data when we load the page. Otherwise we would only be able to retrieve data from the API if we switch to the next or previous month.
Render the Fullcalendar component
return (
<>
<FullCalendar
ref={calendarRef}
locale={"nl-NL"}
plugins={[dayGridPlugin]}
initialView="dayGridMonth"
events={graphData}
height={"650px"}
/>
</>
);
As you can see, we use the graphData as input for the Fullcalendar component and whenever we switch to another period, it’ll retrieve new data from the API, update the state and we will see the data in the calendar.
Full code:
import dayjs from "dayjs";
import { useEffect, useRef, useState } from "react";
import { useLazyGetGraphDataQuery } from "../../../../../slices/GraphSlice";
import FullCalendar, { DatesSetArg } from "@fullcalendar/react"; // must go before plugins
import dayGridPlugin from "@fullcalendar/daygrid"; // a plugin!
import { GraphOutput } from "../../../../../types/graph";
const Calendar = () => {
const calendarRef = useRef<FullCalendar>(null); // Create a ref for accessing the FullCalendar instance
const [graphData, setGraphData] = useState<GraphOutput["result"]>();
const [getGraphData] = useLazyGetGraphDataQuery();
const handleDatesSet = async (arg: DatesSetArg) => {
await getGraphData({
fromDate: dayjs(arg.start).format("YYYY-MM-DD"),
tillDate: dayjs(arg.end).format("YYYY-MM-DD"),
})
.unwrap()
.then((result) => {
setGraphData(result.result);
if (calendarRef.current) {
calendarRef.current.getApi().updateSize();
}
});
};
useEffect(() => {
const calendarApi = calendarRef.current?.getApi(); // Get the FullCalendar API from the ref
if (calendarApi) {
calendarApi.updateSize();
}
if (calendarApi) {
calendarApi.on("datesSet", handleDatesSet); // Attach the datesSet event listener
}
const date = new Date();
handleDatesSet({
end: new Date(date.getFullYear(), date.getMonth() + 1, 0),
start: new Date(date.getFullYear(), date.getMonth(), 1),
endStr: "",
startStr: "",
timeZone: "local",
view: {} as any,
});
return () => {
if (calendarApi) {
calendarApi.off("datesSet", handleDatesSet);
}
};
}, []);
return (
<>
<FullCalendar
ref={calendarRef}
locale={"nl-NL"}
plugins={[dayGridPlugin]}
initialView="dayGridMonth"
events={graphData}
height={"650px"}
/>
</>
);
};
export default Calendar;