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}×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>
)
}
}
实现两个 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
属性(如上所述)- 两者都是同一节点的子节点 - 响应要求。
我有一个自定义组件 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}×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>
)
}
}
实现两个 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
属性(如上所述)- 两者都是同一节点的子节点 - 响应要求。