11import { useEffect , useState } from "preact/hooks" ;
2- import Fuse , { type FuseResult } from "fuse.js" ;
2+ import Fuse from "fuse.js" ;
33import SearchResults from "../SearchResults" ;
44import { defaultLocale } from "@/src/i18n/const" ;
55
@@ -14,8 +14,12 @@ type SearchResult = {
1414 title : string ;
1515 relativeUrl : string ;
1616 description ?: string ;
17+ alias ?: string ;
1718} ;
1819
20+ type SearchIndexEntry = Omit < SearchResult , "id" | "category" | "title" > ;
21+ type SearchIndexData = Record < string , Record < string , SearchIndexEntry > > ;
22+
1923/**
2024 * SearchProvider this component is responsible for handling client-side search.
2125 * It reads the search term from query params and fetches the search index for the current locale.
@@ -28,6 +32,9 @@ const SearchProvider = ({
2832} : SearchProviderProps ) => {
2933 const [ searchTerm , setSearchTerm ] = useState ( "" ) ;
3034 const [ results , setResults ] = useState < SearchResult [ ] > ( [ ] ) ;
35+ const [ searchIndex , setSearchIndex ] = useState < Fuse < SearchResult > | null > (
36+ null ,
37+ ) ;
3138
3239 // Flattens the search index data
3340
@@ -48,16 +55,16 @@ const SearchProvider = ({
4855 }
4956 } , [ searchTerm ] ) ;
5057
51- // Fetch the search index for the current locale and search for the search term
52- // This effect runs whenever the search term or the current locale changes
58+ // Fetch the search index for the current locale
5359 useEffect ( ( ) => {
60+ setSearchIndex ( null ) ;
61+
5462 if ( ! currentLocale ) {
5563 console . warn ( "No locale provided to SearchProvider" ) ;
5664 return ;
5765 }
5866
59- if ( ! searchTerm ) return ;
60- const flattenData = ( data : FuseResult < SearchResult > ) => {
67+ const flattenData = ( data : SearchIndexData ) => {
6168 const flatData : SearchResult [ ] = [ ] ;
6269 let flatId = 0 ;
6370 Object . entries ( data ) . forEach ( ( [ category , entries ] ) => {
@@ -82,12 +89,11 @@ const SearchProvider = ({
8289 return flatData ;
8390 } ;
8491
85- let flatData ;
86-
87- fetch ( `/search-indices/${ currentLocale } .json` )
92+ const controller = new AbortController ( ) ;
93+ fetch ( `/search-indices/${ currentLocale } .json` , { signal : controller . signal } )
8894 . then ( ( response ) => response . json ( ) )
8995 . then ( ( data ) => {
90- flatData = flattenData ( data ) ;
96+ const flatData = flattenData ( data ) ;
9197
9298 const fuseOptions = {
9399 includeScore : true ,
@@ -100,18 +106,26 @@ const SearchProvider = ({
100106 threshold : 0.3 ,
101107 } ;
102108
103- const fuse = new Fuse ( flatData , fuseOptions ) ;
109+ setSearchIndex ( new Fuse ( flatData , fuseOptions ) ) ;
110+ } )
111+ . catch ( ( error ) => {
112+ if ( error . name !== "AbortError" ) {
113+ console . error ( "Error fetching or indexing data:" , error ) ;
114+ }
115+ } ) ;
104116
105- const searchResults = fuse
106- . search ( searchTerm )
107- . map ( ( result ) => result . item ) ;
117+ return ( ) => controller . abort ( ) ;
118+ } , [ currentLocale ] ) ;
108119
109- setResults ( searchResults ) ;
110- } )
111- . catch ( ( error ) =>
112- console . error ( "Error fetching or indexing data:" , error ) ,
113- ) ;
114- } , [ searchTerm , currentLocale ] ) ;
120+ // Search after search term updates
121+ useEffect ( ( ) => {
122+ if ( ! searchTerm || ! searchIndex ) {
123+ setResults ( [ ] ) ;
124+ return ;
125+ }
126+
127+ setResults ( searchIndex . search ( searchTerm ) . map ( ( result ) => result . item ) ) ;
128+ } , [ searchTerm , searchIndex ] ) ;
115129
116130 const handleSearchTermChange = ( term ?: string ) => {
117131 if ( term !== undefined ) {
0 commit comments