ReactJS - пользовательский компонент, запускающий все всплывающие подсказки - PullRequest
0 голосов
/ 27 сентября 2018

У меня есть пользовательский компонент LocationSearch, который является просто оболочкой для местоположения Google Автозаполнение .Иногда адрес длиннее поля ввода, поэтому я применил Reactstrap Tooltip , который показывает полный адрес.Проблема в том, что если у меня есть два из этих компонентов на одной странице, и я наведу курсор мыши на одно из полей ввода, обе подсказки будут запущены, так как на странице также есть еще один компонент LocationSearch.Как я могу только запустить всплывающую подсказку, над которой я нахожу, и не запустить все остальные?

Мой LocationSearch компонент выглядит так:

export default class LocationSearch extends Component {
    constructor(props) {
        super(props);
        this.state = {
            addressSearch: this.props.value || '',
            address: {},
            tooltipKey: false
        };

        this.toggleTooltip = this.toggleTooltip.bind(this);
    }

    toggleTooltip() {
        let tooltipOpen = !this.state.tooltipOpen;
        if (!this.state.addressSearch) {
            tooltipOpen = false;
        }

        this.setState({
            tooltipOpen
        });
    }

    handleAddressSearch = (addressSearch) => {
        this.setState({
            addressSearch,
        });
    };

    handleSelect = (addressSearch) => {
        let scope = this;

        geocodeByAddress(addressSearch)
            .then(results => {
                let street_number, route, city, state, zip, country = "";
                if (results.length > 0) {
                    let result = results[0];
                    for (let i = 0; i < result.address_components.length; i++) {
                        let component = result.address_components[i];
                        for (let x = 0; x < component.types.length; x++) {
                            let type = component.types[x];
                            switch (type) {
                                case "street_number":
                                    street_number = component.long_name || '';
                                    break;
                                case "route":
                                    route = component.long_name || '';
                                    break;
                                case "locality":
                                    city = component.long_name;
                                    break;
                                case "administrative_area_level_1":
                                    state = component.short_name;
                                    break;
                                case "postal_code":
                                    zip = component.long_name;
                                    break;
                                case "country":
                                    country = component.long_name;
                                    break;
                            }
                        }
                    }

                    let address = scope.state.address;
                    if (street_number && route) {
                        address.address1 = street_number + ' ' + route;
                    } else {
                        address.address1 = '';
                    }
                    address.city = city;
                    address.state = state;
                    address.zip = zip;
                    address.country = country;
                    address.googlePlacesId = result.place_id;

                    scope.setState({
                        addressSearch: FormatAddress(address) // just formats a string version of the address object to display in the text box
                    });

                    getLatLng(results[0]).then(function (latLon) {
                        let date = new Date();
                        let url = `https://maps.googleapis.com/maps/api/timezone/json?location=${latLon.lat},${latLon.lng}&timestamp=${date.getTime() / 1000}&key=${GOOGLE_API_CONFIG.mapsKey}`;
                        axios.get(url)
                            .then(function (response) {
                                address.timezone = response.data.timeZoneId;
                                scope.props.handleSelect(address);
                            })
                            .catch(function (error) {
                                console.error("Timezone lookup error:", error);
                                address.timezone = 'US/Arizona';
                                scope.props.handleSelect(address);
                            });
                    })
                }
            })
            .catch(error => {
                console.error('Error', error);
            })
    };


    handleCloseClick = () => {
        this.setState({
            addressSearch: '',
            address: {},
            tooltipOpen: false
        });
    };

    handleError = (status, clearSuggestions) => {
        console.error('Error from Google Maps API', status); // eslint-disable-line no-console
        this.setState({errorMessage: status}, () => {
            clearSuggestions();
        });
    };

    render() {
        if (this.props.hidden) {
            return null;
        }

        return (
            <FormGroup>
                {this.props.label !== '' && (
                    <Label for="address">{this.props.label}</Label>
                )}

                <PlacesAutocomplete
                    onChange={this.handleAddressSearch}
                    value={this.state.addressSearch}
                    onSelect={this.handleSelect}
                    onError={this.props.handleError || this.handleError}
                    shouldFetchSuggestions={!!(this.state.addressSearch && this.state.addressSearch.length > 2)}
                >
                    {({getInputProps, suggestions, getSuggestionItemProps}) => {
                        return (
                            <div className="search-bar-container">
                                <div className="search-input-container" href="#" id="addressTooltip">
                                    <input
                                        {...getInputProps({
                                            placeholder: this.props.placeholder,
                                            className: "search-input"
                                        })}
                                        disabled={this.props.disabled}
                                    />
                                    {this.state.addressSearch && this.state.addressSearch.length > 0 && !this.props.disabled && (
                                        <button
                                            className="clear-button"
                                            onClick={this.handleCloseClick}
                                        >
                                            x
                                        </button>
                                    )}
                                </div>
                                <Tooltip placement="top" isOpen={this.state.tooltipOpen} target="addressTooltip" toggle={this.toggleTooltip}>
                                    {this.state.addressSearch ? this.state.addressSearch : ''}
                                </Tooltip>
                                {suggestions.length > 0 && (
                                    <div className="autocomplete-container">
                                        {suggestions.map(suggestion => {
                                            const className = classNames('suggestion-item', {
                                                'suggestion-item--active': suggestion.active,
                                            });

                                            return (
                                                /* eslint-disable react/jsx-key */
                                                <div
                                                    {...getSuggestionItemProps(suggestion, {className})}
                                                >
                                                    <strong>
                                                        {suggestion.formattedSuggestion.mainText}
                                                    </strong>{' '}
                                                    <small>
                                                        {suggestion.formattedSuggestion.secondaryText}
                                                    </small>
                                                </div>
                                            );
                                            /* eslint-enable react/jsx-key */
                                        })}
                                        <div className="dropdown-footer">
                                            <div>
                                                <img
                                                    src={require('../../assets/img/powered_by_google_default.png')}
                                                    className="dropdown-footer-image"
                                                />
                                            </div>
                                        </div>
                                    </div>
                                )}
                            </div>
                        );
                    }}
                </PlacesAutocomplete>
            </FormGroup>
        )
    }
}

И форма, которая реализует два LocationSearch компоненты, выглядит примерно так:

import LocationSearch from "../../../../components/locationsearch/LocationSearch";

export default class Addresses extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            address1: {},
            address1Str: '',
            address2: {},
            address2Str: '',
        }
    }

    handleAddress1Select = (address1) => {
        this.setState({
            address1,
            address1Str: FormatAddress(address1)
        })
    };

    handleAddress2Select = (address2) => {
        this.setState({
            address2,
            address2Str: FormatAddress(address2)
        })
    };

    render() {
        return (
            <div>
                <LocationSearch
                    label='Address 1'
                    placeholder='Street address...'
                    handleSelect={this.handleAddress1Select}
                    value={this.state.address1Str}
                />

                <LocationSearch
                    label='Address 2'
                    placeholder='Street address...'
                    handleSelect={this.handleAddress2Select}
                    value={this.state.address2Str}
                />
            </div>
        )
    }
}

Вот скриншот всплывающей подсказки над одним из полей адреса.Как видно из другого поля ввода адреса внизу, отображается undefined, и всплывающие подсказки запускаются при наведении курсора на верхнюю.

enter image description here

Есть ли какой-нибудь способ иметь настраиваемое поле всплывающей подсказки в состоянии LocationSearch для каждого экземпляра компонента?

1 Ответ

0 голосов
/ 27 сентября 2018

Обе подсказки прикреплены к одному и тому же id с помощью target="addressTooltip" - это также означает, что есть два div с одинаковым id - они не уникальны - недействительный HTML.

Вам необходимо параметризовать идентификаторы, если вам нужно много экземпляров <LocationSearch/>:

<div className="search-input-container" href="#" id={this.props.inputID}>

<Tooltip placement="top" isOpen={this.state.tooltipOpen} target={this.props.inputID} toggle={this.toggleTooltip}>

и, конечно, передать проп inputID:

            <LocationSearch
                label='Address 1'
                placeholder='Street address...'
                handleSelect={this.handleAddress1Select}
                value={this.state.address1Str}

                inputID='first_id'
                key='first_key'
            />

            <LocationSearch
                label='Address 2'
                placeholder='Street address...'
                handleSelect={this.handleAddress2Select}
                value={this.state.address2Str}

                inputID='second_id'
                key='second_key'
            />

Вы должны использоватьkey свойства для них (как указано выше) - оба являются потомками одного и того же узла - реагируют на требования.

...