Reputation: 2765
I have a custom component LocationSearch
which is just a wrapper around Google's Location AutoComplete. Sometimes the address is longer than the input box so I implemented a Reactstrap Tooltip that shows the full address. The problem is that if I have two of these components on the same page, and I hover over one of the input boxes, both of the tooltips launch since there's another LocationSearch
component on the page as well. How can I only fire the tooltip I am hovering over and not launch all the others?
My LocationSearch
component looks like this:
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}×tamp=${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>
)
}
}
And the form that implements two LocationSearch
components, looks something like this:
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>
)
}
}
Here's a screenshot of the tooltip over one of the address fields. As you can see the other address input box at the bottom shows undefined
and both tooltips are launced when hovering over the one at the top.
Is there any way to have a custom tooltip field in the LocationSearch
state for each instance of the component?
Upvotes: 0
Views: 1108
Reputation: 8428
Both tooltips are attached to the same id
by target="addressTooltip"
- this also means there're two divs with the same id
- they aren't unique - not valid html.
You need to parametrize ids if you need many instances of <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}>
and of course pass prop 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'
/>
You should use key
properties for them (as above) - both are childs of the same node - react requirements.
Upvotes: 1