Skip to content

Instantly share code, notes, and snippets.

@bluebeel
Created July 26, 2020 14:11
Show Gist options
  • Save bluebeel/8b5404360670d5b6d58c17e5f799aa0a to your computer and use it in GitHub Desktop.
Save bluebeel/8b5404360670d5b6d58c17e5f799aa0a to your computer and use it in GitHub Desktop.
autocomplete algolia antd
import { AutoComplete } from 'antd';
import { AutoCompleteProps } from 'antd/lib/auto-complete';
import { Ref, useState } from 'react';
import React from 'react';
import useAutocomplete from '@/components/autocomplete/hook';
export default React.forwardRef((props: AutoCompleteProps, ref: Ref<AutoComplete>) => {
const [search, setSearch] = useState<string>('');
if (!process.env.ALGOLIA_APP_ID || !process.env.ALGOLIA_API_KEY) {
throw new Error(`Missing Algolia credentials!"`);
}
const { results } = useAutocomplete({
appId: process.env.ALGOLIA_APP_ID,
apiKey: process.env.ALGOLIA_API_KEY,
query: search,
});
const uniques = Array.from(new Set(results));
return <AutoComplete ref={ref} allowClear={true} {...props} dataSource={uniques} onSearch={setSearch} />;
});
export interface State<T> {
error?: string;
isLoading: boolean;
results?: T;
}
export type Action<T> = { type: 'OK'; payload: T } | { type: 'LOADING' } | { type: 'ERROR' };
export default function <T>(state: State<T>, action: Action<T>): State<T> {
switch (action.type) {
case 'LOADING':
return { ...state, isLoading: true };
case 'OK':
return { ...state, error: undefined, isLoading: false, results: action.payload };
case 'ERROR':
return { ...state, error: `An error occurred, refresh and try again.`, isLoading: false };
default:
return state;
}
}
import algoliasearch, { Places } from 'algoliasearch';
import { useCallback, useEffect, useReducer } from 'react';
import { getLocale } from 'umi-plugin-locale';
import reducer, { Action, State } from './reducer';
import useDebounce from './useDebounce';
interface AutocompleteParams {
appId: string;
apiKey: string;
query: string;
type?: 'city' | 'country' | 'address' | 'busStop' | 'trainStation' | 'townhall' | 'airport';
}
export default function useAutocomplete(autoCompleteParams: AutocompleteParams): State<Array<string>> {
const { appId, apiKey, type = 'address' } = autoCompleteParams;
const query = useDebounce(autoCompleteParams.query, 500);
const initialState = { isLoading: false };
const [state, dispatch] = useReducer<React.Reducer<State<string[]>, Action<string[]>>>(reducer, initialState);
const places = algoliasearch.initPlaces(appId, apiKey);
const language: 'fr' | 'nl' | 'en' | 'de' | 'es' = { 'fr-FR': 'fr', nl: 'nl', 'en-US': 'en', de: 'de', es: 'es' }[
getLocale()
];
const localizedToAddress = (hit: Places.HitInterface) =>
`${hit.locale_names[0]}, ${hit.postcode ? hit.postcode[0] : ''} ${hit.city ? hit.city[0] : ''}, ${hit.country}`;
const search = useCallback(
async (options: Places.QueryInterface & Places.LanguageInterface) => {
try {
const { hits } = await places.search(options);
dispatch({ payload: hits.map(localizedToAddress), type: 'OK' });
} catch (error) {
dispatch({ type: 'ERROR' });
}
},
[dispatch],
);
useEffect(() => {
if (!state.isLoading) {
dispatch({ type: 'LOADING' });
}
search({ hitsPerPage: 5, language, query, type });
}, [query, apiKey, appId, type]);
return state;
}
import { useEffect, useRef, useState } from 'react';
export default function <T>(value: T, delay: number): T {
const timer = useRef<number | null>(null);
const [state, setState] = useState(value);
useEffect(
() => () => {
if (timer.current) {
window.clearTimeout(timer.current);
}
},
[value, delay],
);
useEffect(() => {
if (timer.current === null) {
setState(value);
} else {
window.clearTimeout(timer.current);
timer.current = null;
}
timer.current = window.setTimeout(() => {
setState(value);
timer.current = null;
}, delay);
}, [value, delay]);
return state;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment