Skip to content

Commit

Permalink
fix search
Browse files Browse the repository at this point in the history
* use state for re-render
* use ref for current query detection in closure functions from previous render
* queue wasn't enough, change to manual debounce of searchLater
* add console debug to check after deployment
  • Loading branch information
Aprillion committed Sep 16, 2023
1 parent ba69b10 commit 0ccdcec
Show file tree
Hide file tree
Showing 2 changed files with 33 additions and 49 deletions.
8 changes: 4 additions & 4 deletions app/components/search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export default function Search({
const [urlSearchParams] = useSearchParams()
const placeholder = urlSearchParams.get('placeholder') ?? 'Search for more questions here...'

const {search, arePendingSearches, results} = useSearch(onSiteAnswersRef, limitFromUrl)
const {search, isPendingSearch, results} = useSearch(onSiteAnswersRef, limitFromUrl)

const searchFn = (rawValue: string) => {
const value = rawValue.trim()
Expand Down Expand Up @@ -108,8 +108,8 @@ export default function Search({
/>
<MagnifyingGlass />
</label>
<div className={`search-loader ${arePendingSearches ? 'loader' : ''}`}> </div>
{arePendingSearches && results.length == 0 && (
<div className={`search-loader ${isPendingSearch ? 'loader' : ''}`}> </div>
{isPendingSearch && results.length == 0 && (
<div className="result-item-box no-questions">Searching for questions...</div>
)}
<AutoHeight>
Expand All @@ -135,7 +135,7 @@ export default function Search({
}}
/>
))}
{showResults && results.length === 0 && !arePendingSearches && <i>(no results)</i>}
{showResults && results.length === 0 && !isPendingSearch && <i>(no results)</i>}
</div>
{!queryFromUrl && (
<button
Expand Down
74 changes: 29 additions & 45 deletions app/hooks/search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -102,24 +102,6 @@ const normalize = (question: string) =>
.replace(/\s+|_|&\s*/g, ' ')
.trim()

const updateQueue = (item: string) => (queue: string[]) => {
if (item == queue[queue.length - 1]) {
// the lastest search is the same a this one - ignore all other searches
// as this is what was desired
return []
}
const pos = queue.indexOf(item)
if (pos < 0) {
// If the item isn't in the queue, then do nothing - it was cleared by previous results
return queue
}
// Remove the first occurance of the item, i.e. the oldest search. It's possible for there
// to be multiple such values, but they weren't the most recent searches, so who cares. They
// might as well be left in though, for bookkeeping reasons
queue.splice(pos, 1)
return queue
}

/**
* Configure the search engine.
*
Expand All @@ -128,11 +110,10 @@ const updateQueue = (item: string) => (queue: string[]) => {
* Searches containing only one or two words will also use the baseline search
*/
export const useSearch = (onSiteQuestions: MutableRefObject<Question[]>, numResults = 5) => {
const resultsProcessor = useRef((data: WorkerMessage): void => {
data // This is here just to get typescript to stop complaining...
})
const tfWorkerRef = useRef<Worker>()
const [queue, setQueue] = useState([] as string[])
const runningQueryRef = useRef<string>() // detect current query in search function from previous render => ref
const timeoutRef = useRef<ReturnType<typeof setTimeout>>() // cancel previous timeout => ref
const [isPendingSearch, setIsPendingSearch] = useState(false) // re-render loading indicator => state
const [results, setResults] = useState([] as SearchResult[])

useEffect(() => {
Expand All @@ -141,53 +122,56 @@ export const useSearch = (onSiteQuestions: MutableRefObject<Question[]>, numResu
worker.addEventListener('message', ({data}) => {
if (data.status == 'ready') {
tfWorkerRef.current = worker
} else if (data.searchResults) {
resultsProcessor.current(data)
} else if (data.userQuery == runningQueryRef.current) {
runningQueryRef.current = undefined
if (data.searchResults) setResults(data.searchResults)
setIsPendingSearch(false)
// TODO: temporary debug, remove if search works well for some time
console.debug('tfWorker search results for:', data.userQuery, data.searchResults)
}
})
}
makeWorker()
}, [resultsProcessor, tfWorkerRef])

resultsProcessor.current = (data) => {
if (!('searchResults' in data)) return
const {searchResults, userQuery} = data

if (queue[queue.length - 1] == userQuery) {
setResults(searchResults)
}
setQueue(updateQueue(userQuery ?? ''))
}
}, [])

const searchLater = (userQuery: string) => {
if (typeof window !== 'undefined') setTimeout(() => search(userQuery), 100)
if (typeof window === 'undefined') return

clearTimeout(timeoutRef.current)
timeoutRef.current = setTimeout(() => {
search(userQuery)
}, 100)
}

// Each search query gets added to the queue of searched items - the idea
// is that only the last item is important - all other searches can be ignored.
const search = (userQuery: string) => {
setQueue([...queue, userQuery])
setIsPendingSearch(true)
const wordCount = userQuery.split(' ').length
if (wordCount > 2) {
if (!tfWorkerRef.current) {
if (runningQueryRef.current || !tfWorkerRef.current) {
searchLater(userQuery)
return
}
runningQueryRef.current = userQuery
tfWorkerRef.current.postMessage({userQuery, numResults})
} else {
if (onSiteQuestions.current.length == 0) {
if (runningQueryRef.current || onSiteQuestions.current.length == 0) {
searchLater(userQuery)
return
}
baselineSearch(userQuery, onSiteQuestions.current, numResults).then((searchResults) =>
resultsProcessor.current({searchResults, userQuery})
)
runningQueryRef.current = userQuery
baselineSearch(userQuery, onSiteQuestions.current, numResults).then((searchResults) => {
runningQueryRef.current = undefined
setResults(searchResults)
setIsPendingSearch(false)
// TODO: temporary debug, remove if search works well for some time
console.debug('baseline search results for:', userQuery, searchResults)
})
}
}

return {
search,
results,
arePendingSearches: queue?.length > 0,
isPendingSearch,
}
}

0 comments on commit 0ccdcec

Please sign in to comment.