mirror of
https://github.com/PeWu/topola-viewer.git
synced 2026-05-27 15:46:14 +00:00
Refactored SearchBarComponent from class-based to functional
This commit is contained in:
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user