superdesk/superdesk-client-core

View on GitHub
scripts/core/ui/components/Autocomplete.tsx

Summary

Maintainability
A
1 hr
Test Coverage
import React from 'react';
import {Select2} from './select2';
import {keyBy} from 'lodash';
import {ListItem, ListItemColumn, ListItemRow} from 'core/components/ListItem';
import {IBaseRestApiResponse, IRestApiResponse} from 'superdesk-api';

interface IProps<T extends IBaseRestApiResponse> {
    placeholder: string;
    query(searchString): Promise<IRestApiResponse<T>>;
    queryById(id): Promise<T>;
    getLabel(item: T): string;
    onSelect(item: T): void;
    selected?: string;
    disabled?: boolean;
    autoFocus?: boolean;
    'data-test-id'?: string;
}

interface IState<T> {
    fetchedItems?: Array<T>;
    loading: boolean;
    selectedItem: T | null;
}

export class AutoComplete<T extends IBaseRestApiResponse> extends React.Component<IProps<T>, IState<T>> {
    private _mounted: boolean;
    constructor(props: IProps<T>) {
        super(props);

        this.state = {
            loading: true,
            selectedItem: null,
        };

        this.queryItems = this.queryItems.bind(this);
        this.fetchSelected = this.fetchSelected.bind(this);
    }

    queryItems(_searchString: string = '') {
        const searchString = _searchString.trim();

        this.setState({loading: true, fetchedItems: null});

        return this.props.query(searchString).then((res) => {
            if (this._mounted) {
                this.setState({
                    fetchedItems: res?._items ?? this.state.fetchedItems,
                    loading: false,
                });
            }
        });
    }

    componentDidMount() {
        this._mounted = true;
        this.queryItems();
        this.fetchSelected();
    }

    componentWillUnmount() {
        this._mounted = false;
    }

    fetchSelected() {
        if (this.props.selected != null) {
            this.props.queryById(this.props.selected).then((selectedItem) => {
                this.setState({selectedItem, loading: false});
            });
        }
    }

    render() {
        const keyedItems: {[key: string]: T} = keyBy(this.state.fetchedItems, (item) => item._id);

        return (
            <Select2
                autoFocus={this.props.autoFocus}
                disabled={this.props.disabled}
                placeholder={(
                    <ListItem fullWidth noBackground noShadow>
                        <ListItemColumn ellipsisAndGrow>
                            <ListItemRow>{this.props.placeholder}</ListItemRow>
                        </ListItemColumn>
                    </ListItem>
                )}
                value={this.props.selected == null ? undefined : this.props.selected}
                valueObject={this.state.selectedItem}
                items={keyedItems}
                getItemValue={(item) => item._id}
                onSelect={(value) => {
                    this.props.onSelect(keyedItems[value]);
                }}
                renderItem={(item) => (
                    <ListItem fullWidth noBackground noShadow>
                        <ListItemColumn ellipsisAndGrow>
                            <ListItemRow>{this.props.getLabel(item)}</ListItemRow>
                        </ListItemColumn>
                    </ListItem>
                )}
                onSearch={(search) => {
                    return this.queryItems(search);
                }}
                loading={this.state.loading}
                data-test-id={this.props['data-test-id']}
            />
        );
    }
}