This project is part of the Grit System Technical Assessment for Front-End Engineers. The main objective is to enhance the calendar component by implementing infinite scrolling using a cursor query parameter in the useRoomRateAvailabilityCalendar
query. Additionally, the candidate will optimize the horizontal scroll behavior of the calendar to ensure smooth and responsive navigation.
To enable infinite scrolling in the calendar component, the following changes were made:
-
Replaced
useQuery
withuseInfiniteQuery
TheuseRoomRateAvailabilityCalendar
hook now usesuseInfiniteQuery
for cursor-based pagination. The query function dynamically updates thecursor
query parameter:return useInfiniteQuery({ queryKey: ["property_room_calendar", params], queryFn: async ({ pageParam }) => { url.search = new URLSearchParams({ start_date: params.start_date, end_date: params.end_date, cursor: pageParam.toString(), }).toString(); return await Fetch<IResponse>({ method: "GET", url, }); }, initialPageParam: 0, getNextPageParam: (lastPage) => lastPage.data.room_categories.length ? lastPage.data.nextCursor : null, });
-
Implemented Intersection Observer
AnIntersectionObserver
was added to detect when the user scrolls to the bottom of the content. ABox
component is placed at the bottom of the page, and its visibility is tracked using aref
. When the observer detects the box is in view, it triggers thefetchNextPage
function to load more data:const observerRef = useRef<HTMLDivElement | null>(null); useEffect(() => { const observer = new IntersectionObserver( (entries) => { const target = entries[0]; if ( target.isIntersecting && !room_calendar.isFetching && room_calendar.hasNextPage ) { if (process.env.NODE_ENV === "development") { console.log("Fetching next page..."); } room_calendar.fetchNextPage(); } }, { threshold: 0.1 } ); if (observerRef.current) observer.observe(observerRef.current); return () => { if (observerRef.current) observer.unobserve(observerRef.current); }; }, [room_calendar.isFetching, room_calendar.hasNextPage]);
-
Updated Rendering Logic
Updated the rendering logic to flatten and display data from all fetched pages:{ room_calendar.isSuccess && room_calendar.data?.pages?.length > 0 ? room_calendar.data.pages .flatMap((page) => page.data.room_categories) .map((room_category, index, array) => ( <RoomRateAvailabilityCalendar key={index} index={index} InventoryRefs={InventoryRefs} isLastElement={index === array.length - 1} room_category={room_category} handleCalenderScroll={handleCalenderScroll} /> )) : null; }
To ensure smooth horizontal scrolling, the following adjustments were made:
-
Scroll Calculation
Adjusted the calculation ofscrollLeft
to account for bothdeltaX
anddeltaY
. This ensures vertical mouse scrolling contributes to horizontal navigation when appropriate:useEffect(() => { const { current: rootContainer } = rootContainerRef; if (rootContainer) { const handler = (e: WheelEvent) => { if ( mainGridContainerRef.current && InventoryRefs.current && calenderMonthsRef.current && calenderDatesRef.current ) { console.log(e.deltaX, e.deltaY); if (e.deltaY !== 0 || e.deltaX !== 0) { e.preventDefault(); let { scrollLeft } = mainGridContainerRef.current; scrollLeft += e.deltaX + e.deltaY; InventoryRefs.current.forEach((ref) => { if (ref.current) { ref.current.scrollTo({ scrollLeft }); } }); calenderMonthsRef.current.scrollTo(scrollLeft); calenderDatesRef.current.scrollTo({ scrollLeft }); } } }; rootContainer.addEventListener("wheel", handler); return () => rootContainer.removeEventListener("wheel", handler); } });
- Replaced
useQuery
withuseInfiniteQuery
for cursor-based pagination. - Added an
IntersectionObserver
to detect the end of the content and trigger the next page fetch. - Updated the rendering logic to support dynamic data loading.
- Enhanced the
scrollLeft
calculation to include bothdeltaX
anddeltaY
, ensuring smooth and responsive scrolling.