Assistance Needed with Data Pagination in Syncfusion Scheduler using Firebase Firestore

Hi

I’m currently working on integrating a calendar component using the Syncfusion Scheduler and Firebase Firestore for data management. I’m having difficulty implementing data pagination based on the current view days in the agenda.

Initially, I attempted to manage the start date and end date using React state in the OnActionBegin event. However, setting state in this function caused the entire component to re-render, which led to unexpected bugs. To work around this issue, I implemented the DataManager, but I'm still struggling with updating it effectively when navigating between dates in the calendar.

I would appreciate guidance on how to effectively manage state and implement data pagination with Firestore to ensure smooth performance.

Link to the code

jpeg-optimizer_imagejpe.jpg



5 Replies

SR Swathi Ravi Syncfusion Team September 30, 2024 12:31 PM UTC

Hi Aviv Daniel,

Thank you for reaching out to us!

To effectively handle data pagination based on the current view's start and end dates in the Syncfusion Scheduler, we recommend using the UrlAdaptor. This adaptor automatically includes the start and end date range of the current view in the parameters whenever there is a change in the date or view, including during the initial rendering. This allows you to handle pagination smoothly without manually managing the state in the OnActionBegin event.



You can find a working example of this implementation here:
Please feel free to reach out if you have any further questions or need additional assistance. We're happy to help!

Regards,
Swathi


AD Aviv Daniel replied to Swathi Ravi October 9, 2024 12:53 PM UTC

Hi, thank you for your response!

After reading your reply, I created a custom adapter that extends the urlAdaptor. As I mentioned, I'm using Firebase Firestore, so I don’t have a standard URL endpoint to call directly. Instead, I need to use Firebase's getDocs method with my collection name, rather than using fetch or any similar approach.

I implemented it this way, but when processQuery returns a promise, and then processResponse also returns a promise, the data doesn’t display in the calendar. Am I missing something or doing something wrong?


import React, {forwardRef, useCallback, useEffect, useMemo, useRef, useState} from 'react';
import {
ScheduleComponent,
ViewsDirective,
ViewDirective,
Inject,
Agenda, navigating,
} from '@syncfusion/ej2-react-schedule';
import {registerLicense} from '@syncfusion/ej2-base';
import dayjs from 'dayjs';
import {PageHero} from "../components/Ui";
import {iconsNames} from "../components/Ui/CardGroups/consts";
import {DataManager} from '@syncfusion/ej2-data';
import {getDocsWhereMultiple, getModelSessionsForSession, mapAsync} from "../services";
import {COLLECTIONS} from "../constants/collections";
import {JsonAdaptor, UrlAdaptor} from "@syncfusion/ej2-data/src/adaptors";

registerLicense(process.env.REACT_APP_SYNCFUSION_LICENSE);

// Custom Firestore adaptor
class FirestoreAdaptor extends UrlAdaptor {
constructor() {
super();
}

// Override processQuery to fetch data from Firestore
async processQuery(dm, query) {
// Extract the start and end date from the query (this is usually passed from Syncfusion's scheduler)
const startDate = query.params.find(param => param.key === 'StartDate').value;
const endDate = query.params.find(param => param.key === 'EndDate').value;

// Prepare Firestore conditions
const conditions = [
{field: 'date', operator: '>=', value: new Date(startDate)},
{field: 'date', operator: '<=', value: new Date(endDate)}
];
const events = await getDocsWhereMultiple(COLLECTIONS.sessions, conditions);

return events.map(session => {
const startDate = new Date(dayjs(session.date).hour(9).minute(0).toDate());
return {
Id: session.id,
Subject: session.production,
StartTime: startDate,
EndTime: startDate,
IsAllDay: true,
};
});
}

convertToQueryString(request, query) {
return ''; // Not needed for Firestore
}

async processResponse(data) {
const res = await data;
return res
}
}

const Calendar = () => {
const [currentView, setCurrentView] = useState('Agenda');
const scheduleRef = useRef(null);


const [dataManager, setDataManager] = useState(null);

useEffect(() => {
// Initialize DataManager and fetch initial appointments
const initializedDateManager = async () => {
const manager = new DataManager(
{
adaptor: new FirestoreAdaptor()
})
await manager.ready;
setDataManager(manager);
};
initializedDateManager();
}, []);

return (
<div className="Calendar">
<PageHero loading={false} icon={iconsNames.calendar} header="לוח שנה"/>
<ScheduleComponent
ref={scheduleRef}
width='100%'
enablePersistence
height='500px'
selectedDate={new Date()}
enableRtl
readonly
eventSettings={{dataSource: dataManager}}
>
<ViewsDirective>
<ViewDirective option={currentView} isSelected enableLazyLoading/>
</ViewsDirective>
<Inject services={[Agenda]}/>
</ScheduleComponent>
</div>
);
};

export default Calendar;


SR Swathi Ravi Syncfusion Team October 10, 2024 02:58 PM UTC

Aviv,

Thank you for your detailed explanation and for sharing your code snippets!

It appears that the issue you're experiencing may be related to how you're using the DataManager within your asynchronous function, especially when combined with state variables. Instead of assigning the DataManager instance to a state variable, you can simply assign it directly to a constant like this:


    const dataManager = new DataManager({
        adaptor: new FirestoreAdaptor(), // Custom FirestoreAdaptor
    });

This approach can help ensure that the DataManager is initialized correctly and avoid potential issues with promise handling that may arise from the state variable's asynchronous nature.
If you continue to face difficulties, please let us know, and we’ll be glad to assist you further.


AD Aviv Daniel October 13, 2024 03:23 PM UTC

Hi again! I really appreciate your support. I tried to follow your instructions closely, but it still doesn't seem to work. I'm pasting the pseudocode here again to help clarify what I'm trying to implement. As I mentioned, when the process data returns a Promise, the appointments aren’t displaying.


import React, {useRef, useState} from 'react';
import {Agenda, Inject, ScheduleComponent, ViewDirective, ViewsDirective,} from '@syncfusion/ej2-react-schedule';
import {registerLicense} from '@syncfusion/ej2-base';
import dayjs from 'dayjs';
import {PageHero} from "../components/Ui";
import {iconsNames} from "../components/Ui/CardGroups/consts";
import {DataManager} from '@syncfusion/ej2-data';
import {getDocsWhereMultiple} from "../services";
import {COLLECTIONS} from "../constants/collections";
import {UrlAdaptor} from "@syncfusion/ej2-data/src/adaptors";

registerLicense(process.env.REACT_APP_SYNCFUSION_LICENSE);

// Custom Firestore adaptor
class FirestoreAdaptor extends UrlAdaptor {
constructor() {
super();
}

// Override processQuery to fetch data from Firestore
processQuery(dm, query) {
// Extract the start and end date from the query (this is usually passed from Syncfusion's scheduler)
const startDate = query.params.find(param => param.key === 'StartDate').value;
const endDate = query.params.find(param => param.key === 'EndDate').value;


// This is a mock demonstrating fetching documents from firebase firestore asynchronously.
return new Promise(resolve => {
resolve([{
Id: '1',
Subject: 'Paris',
StartTime: new Date(),
EndTime: new Date(),
}])
})

}

convertToQueryString(request, query) {
return ''; // Not needed for Firestore
}

async processResponse(data) {
const result = await data
return result
}
}

const dataManager = new DataManager(
{
adaptor: new FirestoreAdaptor()
});

const Calendar = () => {
const [currentView, setCurrentView] = useState('Agenda');
const scheduleRef = useRef(null);

return (
<div className="Calendar">
<PageHero loading={false} icon={iconsNames.calendar} header="לוח שנה"/>
<ScheduleComponent
ref={scheduleRef}
width='100%'
enablePersistence
height='500px'
selectedDate={new Date()}
enableRtl
readonly
eventSettings={{dataSource: dataManager}}
>
<ViewsDirective>
<ViewDirective option={currentView} isSelected enableLazyLoading/>
</ViewsDirective>
<Inject services={[Agenda]}/>
</ScheduleComponent>
</div>
);
};

export default Calendar;




SR Swathi Ravi Syncfusion Team October 18, 2024 07:02 AM UTC

Aviv Daniel,
You are using a custom implementation by extending the UrlAdaptor for Firestore integration. Since the Scheduler component is not displaying the data even though the data is correctly received in the processResponse method, there are a few points to consider:
  1. Url Requirement: When using the UrlAdaptor (or any adaptor that extends from it), a valid dataService URL is usually required. Since Firestore does not use a traditional REST API approach for fetching data, so the UrlAdaptor might not be fit for customer requirement.
  2. CustomDataAdaptor: In cases where Firestore or a similar database is used, the CustomDataAdaptor is a better fit because it allows you to handle data retrieval and binding in a custom way without relying on an external URL. It provides more flexibility, allowing you to bind the Firestore data directly to the Scheduler through custom logic.
  3. Asynchronous Data Handling: If Firestore is being used, the data fetching is likely asynchronous. You can use the CustomDataAdaptor to manage promise-based data binding to the Scheduler, ensuring the data is properly fetched and displayed.
Solution:
 
  • You should switch from UrlAdaptor to CustomDataAdaptor to handle the Firestore data fetching.
  • Ensure the data is correctly formatted in the way the Scheduler expects and use promise-based/Firestore handling to retrieve and display the data.
Please review the CustomDataAdaptor documentation and JsonAdaptor for a detailed guide on implementing this. Also, you can refer to the modified sample for async dataSource binding:
By using this approach, you should be able to resolve the issue and display the Firestore data correctly in the Scheduler.

Loader.
Up arrow icon