Skip to content

Instantly share code, notes, and snippets.

@GriffinSauce
Last active November 28, 2020 18:25
Show Gist options
  • Save GriffinSauce/0451d328511b4e82469f4e9f688e5118 to your computer and use it in GitHub Desktop.
Save GriffinSauce/0451d328511b4e82469f4e9f688e5118 to your computer and use it in GitHub Desktop.
A hook and context for algoliasearch/lite
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