1- import React , { createContext , useContext , useEffect } from "react" ;
2- import useSWR from "swr" ;
1+ import React , { createContext , useContext , useEffect , useCallback } from "react" ;
2+ import useSWRInfinite from "swr/infinite " ;
33import ResultsList from "./ResultsList" ;
44import styled from "@emotion/styled" ;
55import { DegreePlan , Rule , Fulfillment } from "@/types" ;
@@ -153,9 +153,16 @@ export const useDebounce = (value: any, delay: number) => {
153153 return debouncedValue ;
154154}
155155
156- const buildSearchKey = ( ruleId : Rule [ "id" ] | null , query : string ) : string | null => {
157- return query . length >= 3 || ruleId ? `api/base/all/search/courses?search=${ query } ${ ruleId ? `&rule_ids=${ ruleId } ` : "" } ` : null
158- }
156+ const PAGE_SIZE = 50 ;
157+
158+ const buildSearchKey = ( ruleId : Rule [ "id" ] | null , query : string ) =>
159+ ( pageIndex : number , previousPageData : any ) : string | null => {
160+ if ( ! ( query . length >= 3 || ruleId ) ) return null ;
161+ // Stop fetching if we got fewer results than a full page
162+ if ( previousPageData && ! previousPageData . next ) return null ;
163+ const page = pageIndex + 1 ; // DRF pages are 1-indexed
164+ return `api/base/all/search/courses?search=${ query } ${ ruleId ? `&rule_ids=${ ruleId } ` : "" } &page=${ page } &page_size=${ PAGE_SIZE } ` ;
165+ }
159166
160167interface SearchResultsProps {
161168 ruleId : Rule [ "id" ] | null ,
@@ -166,16 +173,33 @@ interface SearchResultsProps {
166173const SearchResults = ( { ruleId, query, activeDegreeplanId, fulfillments } : SearchResultsProps ) => {
167174 const DISABLE_SEARCH = false
168175 const debouncedQuery = useDebounce ( query , 400 )
169- const { data : courses = [ ] , isLoading : isLoadingCourses , error } = useSWR ( DISABLE_SEARCH ? null : buildSearchKey ( ruleId , debouncedQuery ) ) ;
176+ const { data, size, setSize, isLoading : isLoadingCourses , isValidating } = useSWRInfinite (
177+ DISABLE_SEARCH ? ( ) => null : buildSearchKey ( ruleId , debouncedQuery ) ,
178+ { revalidateFirstPage : false }
179+ ) ;
180+
181+ const courses = data ? data . flatMap ( ( page ) => page . results ?? [ ] ) : [ ] ;
182+ const isLoadingMore = size > 1 && isValidating && data && typeof data [ size - 1 ] === "undefined" ;
183+ const hasMore = data ? ! ! data [ data . length - 1 ] ?. next : false ;
184+
185+ const loadMore = useCallback ( ( ) => {
186+ if ( ! isValidating && hasMore ) {
187+ setSize ( ( s ) => s + 1 ) ;
188+ }
189+ } , [ isValidating , hasMore , setSize ] ) ;
190+
170191 return (
171192 < >
172193 < SearchPanelResult >
173194 < ResultsList
174- activeDegreeplanId = { activeDegreeplanId }
175- ruleId = { ruleId }
195+ activeDegreeplanId = { activeDegreeplanId }
196+ ruleId = { ruleId }
176197 courses = { courses }
177198 fulfillments = { fulfillments }
178199 isLoading = { isLoadingCourses }
200+ isLoadingMore = { ! ! isLoadingMore }
201+ hasMore = { hasMore }
202+ loadMore = { loadMore }
179203 />
180204 </ SearchPanelResult >
181205 </ >
0 commit comments