ReactJS - 自定义组件启动所有工具提示

ReactJS - custom component launching all tooltips

我有一个自定义组件 LocationSearch,它只是 Google 的 Location AutoComplete. Sometimes the address is longer than the input box so I implemented a 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,当鼠标悬停在顶部的输入框上时,两个工具提示都会被启动。

有什么方法可以让组件的每个实例在 LocationSearch 状态下有一个自定义工具提示字段?

两个工具提示都由 target="addressTooltip" 附加到同一个 id - 这也意味着有两个 div 具有相同的 id - 它们不是唯一的 - 无效 html.

如果需要 <LocationSearch/> 的多个实例,则需要对 ID 进行参数化:

<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 属性(如上所述)- 两者都是同一节点的子节点 - 响应要求。