Refactored SearchBarComponent from class-based to functional

This commit is contained in:
Przemek Wiech
2021-11-02 12:21:17 +01:00
parent 9d8bd01a14
commit 632964e8b9

View File

@@ -1,4 +1,3 @@
import * as React from 'react';
import debounce from 'debounce'; import debounce from 'debounce';
import {analyticsEvent} from '../util/analytics'; import {analyticsEvent} from '../util/analytics';
import {buildSearchIndex, SearchIndex, SearchResult} from './search_index'; import {buildSearchIndex, SearchIndex, SearchResult} from './search_index';
@@ -6,8 +5,8 @@ import {formatDateOrRange} from '../util/date_util';
import {IndiInfo, JsonGedcomData} from 'topola'; import {IndiInfo, JsonGedcomData} from 'topola';
import {injectIntl, WrappedComponentProps} from 'react-intl'; import {injectIntl, WrappedComponentProps} from 'react-intl';
import {JsonIndi} from 'topola'; import {JsonIndi} from 'topola';
import {RouteComponentProps} from 'react-router-dom'; import {Search, SearchResultProps} from 'semantic-ui-react';
import {Search, SearchProps, SearchResultProps} from 'semantic-ui-react'; import {useEffect, useRef, useState} from 'react';
function getNameLine(result: SearchResult) { function getNameLine(result: SearchResult) {
const name = [result.indi.firstName, result.indi.lastName].join(' ').trim(); const name = [result.indi.firstName, result.indi.lastName].join(' ').trim();
@@ -27,25 +26,15 @@ interface Props {
onSelection: (indiInfo: IndiInfo) => void; onSelection: (indiInfo: IndiInfo) => void;
} }
interface State {
searchResults: SearchResultProps[];
}
/** Displays and handles the search box in the top bar. */ /** Displays and handles the search box in the top bar. */
class SearchBarComponent extends React.Component< function SearchBarComponent(props: WrappedComponentProps & Props) {
RouteComponentProps & WrappedComponentProps & Props, const [searchResults, setSearchResults] = useState<SearchResultProps[]>([]);
State const [searchString, setSearchString] = useState('');
> { const searchIndex = useRef<SearchIndex>();
state: State = {
searchResults: [],
};
searchRef?: {setValue(value: string): void}; function getDescriptionLine(indi: JsonIndi) {
searchIndex?: SearchIndex; const birthDate = formatDateOrRange(indi.birth, props.intl);
const deathDate = formatDateOrRange(indi.death, props.intl);
private getDescriptionLine(indi: JsonIndi) {
const birthDate = formatDateOrRange(indi.birth, this.props.intl);
const deathDate = formatDateOrRange(indi.death, this.props.intl);
if (!deathDate) { if (!deathDate) {
return birthDate; return birthDate;
} }
@@ -53,74 +42,62 @@ class SearchBarComponent extends React.Component<
} }
/** Produces an object that is displayed in the Semantic UI Search results. */ /** Produces an object that is displayed in the Semantic UI Search results. */
private displaySearchResult(result: SearchResult) { function displaySearchResult(result: SearchResult): SearchResultProps {
return { return {
id: result.id, id: result.id,
key: result.id, key: result.id,
title: getNameLine(result), title: getNameLine(result),
description: this.getDescriptionLine(result.indi), description: getDescriptionLine(result.indi),
}; } as SearchResultProps;
} }
/** On search input change. */ /** On search input change. */
private handleSearch(input: string | undefined) { function handleSearch(input: string | undefined) {
if (!input) { if (!input) {
return; return;
} }
const results = this.searchIndex!.search(input).map((result) => const results = searchIndex
this.displaySearchResult(result), .current!.search(input)
); .map((result) => displaySearchResult(result));
this.setState(Object.assign({}, this.state, {searchResults: results})); setSearchResults(results);
} }
const debouncedHandleSearch = useRef(debounce(handleSearch, 200));
/** On search result selected. */ /** On search result selected. */
private handleResultSelect(id: string) { function handleResultSelect(id: string) {
analyticsEvent('search_result_selected'); analyticsEvent('search_result_selected');
this.props.onSelection({id, generation: 0}); props.onSelection({id, generation: 0});
this.searchRef!.setValue(''); setSearchString('');
} }
private initializeSearchIndex() { /** On search string changed. */
this.searchIndex = buildSearchIndex(this.props.data); function onChange(value: string) {
debouncedHandleSearch.current(value);
setSearchString(value);
} }
componentDidMount() { // Initialize the search index.
this.initializeSearchIndex(); useEffect(() => {
} searchIndex.current = buildSearchIndex(props.data);
}, [props.data]);
componentDidUpdate(prevProps: Props) { return (
if (prevProps.data !== this.props.data) { <Search
this.initializeSearchIndex(); onSearchChange={(_, data) => onChange(data.value!)}
} onResultSelect={(_, data) => handleResultSelect(data.result.id)}
} results={searchResults}
noResultsMessage={props.intl.formatMessage({
render() { id: 'menu.search.no_results',
return ( defaultMessage: 'No results found',
<Search })}
onSearchChange={debounce( placeholder={props.intl.formatMessage({
(_: React.MouseEvent<HTMLElement>, data: SearchProps) => id: 'menu.search.placeholder',
this.handleSearch(data.value), defaultMessage: 'Search for people',
200, })}
)} selectFirstResult={true}
onResultSelect={(_, data) => this.handleResultSelect(data.result.id)} value={searchString}
results={this.state.searchResults} id="search"
noResultsMessage={this.props.intl.formatMessage({ />
id: 'menu.search.no_results', );
defaultMessage: 'No results found',
})}
placeholder={this.props.intl.formatMessage({
id: 'menu.search.placeholder',
defaultMessage: 'Search for people',
})}
selectFirstResult={true}
ref={(ref) =>
(this.searchRef = (ref as unknown) as {
setValue(value: string): void;
})
}
id="search"
/>
);
}
} }
export const SearchBar = injectIntl(SearchBarComponent); export const SearchBar = injectIntl(SearchBarComponent);