Last active
November 28, 2020 18:25
-
-
Save GriffinSauce/0451d328511b4e82469f4e9f688e5118 to your computer and use it in GitHub Desktop.
A hook and context for algoliasearch/lite
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { useState, useEffect, useContext, useRef } from 'react'; | |
import algoliasearch from 'algoliasearch/lite'; | |
const algoliaClient = algoliasearch(ALGOLIA_APP_ID, ALGOLIA_API_KEY); | |
const SHOW_LOADING_AFTER_MS = 300; | |
/** | |
* Initialize a search | |
* | |
* @example: | |
* const { searchState, setSearchState, results, isLoading } = useAlgoliaSearch(); | |
* const onSubmit = (query) => { | |
* setSearchState(current => ({ ...current, query })); | |
* } | |
*/ | |
export const useAlgoliaSearch = ({ | |
initialSearchState = {}, | |
initialResults = null, // For SSR | |
onResults, // Eg. to track to analytics when results are shown | |
}) => { | |
if (!initialSearchState.index) | |
throw new Error('initialSearchState with index is required'); | |
const skip = useRef(!!initialResults); | |
// The search query and options | |
const [searchState, setSearchState] = useState({ | |
...initialSearchState, | |
context: initialSearchState.context || {}, // Put additional custom data in searchState.context - the search lib will keep it in state and won't touch it | |
getRankingInfo: true, | |
}); | |
const [results, setResults] = useState(initialResults); | |
const [isLoading, setLoading] = useState(false); | |
useEffect( | |
() => { | |
const { index, context, ...searchOptions } = searchState; // Algolia errors on unknown parameters so filter them out | |
const getResults = async () => { | |
// Show loading after a while to prevent jumpyness | |
const loadingTimeout = setTimeout( | |
() => setLoading(true), | |
SHOW_LOADING_AFTER_MS, | |
); | |
// Get results | |
try { | |
const searchIndex = algoliaClient.initIndex(index); | |
const res = await searchIndex.search(searchOptions); | |
setResults(res); | |
clearTimeout(loadingTimeout); | |
setLoading(false); | |
if (onResults) onResults({ searchState, results: res }); | |
} catch (error) { | |
if (error.message.toLowerCase().includes('unknown parameter')) { | |
error.message = `${ | |
error.message | |
} - if you want to add custom (non-Algolia) parameters, use searchState.context`; | |
} | |
console.error(`useAlgoliaSearch error: ${error.message}`); | |
// Send error to sentry or whatever here | |
clearTimeout(loadingTimeout); | |
setLoading(false); | |
} | |
}; | |
// If initialResults is provided we can skip once | |
if (skip.current) { | |
skip.current = false; | |
return; | |
} | |
getResults(); | |
}, | |
[searchState, onResults], | |
); | |
return { | |
searchState, | |
setSearchState, | |
results, | |
isLoading, | |
}; | |
}; | |
/** | |
* Context to access/mutate search down the component tree | |
* | |
* @example - Create the context: | |
* const searchContext = useAlgoliaSearch(); | |
* retusn ( | |
* <SearchContext.Provider value={searchContext}> | |
* <SearchBox /> | |
* </SearchContext.Provider> | |
* ); | |
*/ | |
export const SearchContext = React.createContext(); | |
/** | |
* Hook to easily grab search props from context | |
* Returns whatever is passed into SearchContext.Provider | |
*/ | |
export const useSearchContext = () => { | |
const searchContext = useContext(SearchContext); | |
if (!searchContext) | |
throw new Error( | |
'No search context found, make sure the component is wrapped with a <SearchContext.Provider>', | |
); | |
return searchContext; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment