|
1 | 1 | "use client"; |
2 | 2 | import { api } from "@albert-plus/server/convex/_generated/api"; |
3 | 3 | import type { Id } from "@albert-plus/server/convex/_generated/dataModel"; |
4 | | -import { useVirtualizer } from "@tanstack/react-virtual"; |
5 | 4 | import { useMutation, useQuery } from "convex/react"; |
6 | 5 | import { ConvexError } from "convex/values"; |
7 | | -import React, { useEffect, useState } from "react"; |
| 6 | +import { useEffect, useRef, useState } from "react"; |
8 | 7 | import { toast } from "sonner"; |
9 | 8 | import { Button } from "@/components/ui/button"; |
10 | 9 | import { useSearchParam } from "@/hooks/use-search-param"; |
@@ -91,33 +90,28 @@ const CourseSelector = ({ |
91 | 90 | setHoveredSection(section); |
92 | 91 | }; |
93 | 92 |
|
94 | | - const parentRef = React.useRef<HTMLDivElement>(null); |
| 93 | + const observerTarget = useRef<HTMLDivElement>(null); |
95 | 94 |
|
96 | | - const rowVirtualizer = useVirtualizer({ |
97 | | - count: filteredData.length, |
98 | | - getScrollElement: () => parentRef.current, |
99 | | - estimateSize: () => 100, |
100 | | - overscan: 5, |
101 | | - gap: 8, |
102 | | - }); |
103 | | - |
104 | | - useEffect(() => { |
105 | | - onHover?.(hoveredSection); |
106 | | - }, [hoveredSection, onHover]); |
107 | | - |
108 | | - // https://tanstack.com/virtual/latest/docs/framework/react/examples/infinite-scroll |
109 | | - // biome-ignore lint/correctness/useExhaustiveDependencies: It's in Tanstack doc |
110 | 95 | useEffect(() => { |
111 | | - const [lastItem] = [...rowVirtualizer.getVirtualItems()].reverse(); |
| 96 | + const observer = new IntersectionObserver( |
| 97 | + (entries) => { |
| 98 | + if (entries[0].isIntersecting && status === "CanLoadMore") { |
| 99 | + loadMore(200); |
| 100 | + } |
| 101 | + }, |
| 102 | + { threshold: 0.1 }, |
| 103 | + ); |
112 | 104 |
|
113 | | - if (!lastItem) { |
114 | | - return; |
| 105 | + if (observerTarget.current) { |
| 106 | + observer.observe(observerTarget.current); |
115 | 107 | } |
116 | 108 |
|
117 | | - if (lastItem.index >= filteredData.length - 1 && status === "CanLoadMore") { |
118 | | - loadMore(200); |
119 | | - } |
120 | | - }, [status, loadMore, filteredData.length, rowVirtualizer.getVirtualItems()]); |
| 109 | + return () => observer.disconnect(); |
| 110 | + }, [status, loadMore]); |
| 111 | + |
| 112 | + useEffect(() => { |
| 113 | + onHover?.(hoveredSection); |
| 114 | + }, [hoveredSection, onHover]); |
121 | 115 |
|
122 | 116 | const handleSectionSelect = async (offering: CourseOffering) => { |
123 | 117 | if (offering.status === "closed") { |
@@ -350,44 +344,20 @@ const CourseSelector = ({ |
350 | 344 | )} |
351 | 345 |
|
352 | 346 | {filteredData.length > 0 && ( |
353 | | - <div |
354 | | - ref={parentRef} |
355 | | - className="overflow-auto no-scrollbar w-full flex-1 min-h-0" |
356 | | - > |
357 | | - <div |
358 | | - className="relative w-full" |
359 | | - style={{ |
360 | | - height: `${rowVirtualizer.getTotalSize()}px`, |
361 | | - }} |
362 | | - > |
363 | | - {rowVirtualizer.getVirtualItems().map((virtualItem) => { |
364 | | - const course = filteredData[virtualItem.index]; |
365 | | - |
366 | | - return ( |
367 | | - <div |
368 | | - key={virtualItem.key} |
369 | | - data-index={virtualItem.index} |
370 | | - ref={rowVirtualizer.measureElement} |
371 | | - className="absolute top-0 left-0 w-full" |
372 | | - style={{ |
373 | | - transform: `translateY(${virtualItem.start}px)`, |
374 | | - }} |
375 | | - > |
376 | | - <CourseCard |
377 | | - course={course} |
378 | | - isExpanded={isExpanded(course.code)} |
379 | | - selectedClassNumbers={selectedClassNumbers} |
380 | | - onToggleExpand={toggleCourseExpansion} |
381 | | - onSectionSelect={handleSectionSelect} |
382 | | - onSectionSelectAsAlternative={ |
383 | | - handleSectionSelectAsAlternative |
384 | | - } |
385 | | - onSectionHover={handleSectionHover} |
386 | | - /> |
387 | | - </div> |
388 | | - ); |
389 | | - })} |
390 | | - </div> |
| 347 | + <div className="overflow-auto no-scrollbar w-full flex-1 min-h-0 space-y-2"> |
| 348 | + {filteredData.map((course) => ( |
| 349 | + <CourseCard |
| 350 | + key={course._id} |
| 351 | + course={course} |
| 352 | + isExpanded={isExpanded(course.code)} |
| 353 | + selectedClassNumbers={selectedClassNumbers} |
| 354 | + onToggleExpand={toggleCourseExpansion} |
| 355 | + onSectionSelect={handleSectionSelect} |
| 356 | + onSectionSelectAsAlternative={handleSectionSelectAsAlternative} |
| 357 | + onSectionHover={handleSectionHover} |
| 358 | + /> |
| 359 | + ))} |
| 360 | + <div ref={observerTarget} className="h-1" /> |
391 | 361 | </div> |
392 | 362 | )} |
393 | 363 |
|
|
0 commit comments