I'm using google-map-react
to display places on google map with auto-suggestion feature, following example here: https://www.freakyjolly.com/google-maps-in-react-example-application. I'm using Typescript so my code is different from the blog.
I'm having a problem right now : console.log(autoComplete);
in function onPlaceChanged
print undefined
. Any idea why this is happening, and how can I fix it?
const AutoComplete = ({map, mapApi, addplace} : {map: any, mapApi: any, addplace: any}) => {
const options = {
// restrict your search to a specific type of result
types: ['address'],
// restrict your search to a specific country, or an array of countries
// componentRestrictions: { country: ['gb', 'us'] },
};
let [searchInput, setSearchInput]= React.useState<HTMLInputElement | null>();
let [autoComplete, setAutoComplete] = React.useState<any>();
React.useEffect(
() => {
const autoCompleteInstance = new mapApi.places.Autocomplete(
searchInput!,
options
);
autoCompleteInstance.addListener('place_changed', onPlaceChanged);
autoCompleteInstance.bindTo('bounds', map);
setAutoComplete(autoCompleteInstance);
console.log(autoCompleteInstance);
// returned function will be called on component unmount
return () => {
mapApi.event.clearInstanceListeners(searchInput!);
}
},
[]
);
const onPlaceChanged = () => {
console.log(autoComplete);
const place : google.maps.places.PlaceResult = autoComplete.getPlace();
if (!place.geometry) return;
if (place.geometry.viewport) {
map.fitBounds(place.geometry.viewport);
} else {
map.setCenter(place.geometry.location);
map.setZoom(17);
}
addplace(place);
searchInput!.blur();
};
const clearSearchBox = () => {
searchInput!.value = '';
}
return (
<div className='relative items-center justify-center w-full p-5 text-center'>
<input
className="w-4/5 text-base"
ref={(ref) => {
searchInput = ref;
}}
type="text"
onFocus={clearSearchBox}
placeholder="Enter a location"
/>
</div>
);
}
console.log(autoComplete);
in functiononPlaceChanged
print undefined. Any idea why this is happening, and how can I fix it?
This is because you are closing over the initial undefined autoComplete
state in the callback.
let [autoComplete, setAutoComplete] = React.useState<any>(); // undefined
...
autoCompleteInstance.addListener('place_changed', onPlaceChanged); // initial "instance"
...
const onPlaceChanged = () => {
console.log(autoComplete); // initial undefined value closed over in scope
...
};
In other words, you are accessing a stale enclosure of the autoComplete
state.
I suggest using a React ref to hold the autoCompleteInstance
instead of holding it in state. A new mapApi.places.Autocomplete
instance can be constructing and stored in the ref on the initial render, the effect is still used to add the listener and bind the instance to map
.
Example:
const autoCompleteInstanceRef = React.useRef<any>(
new mapApi.places.Autocomplete(searchInput!, options)
);
React.useEffect(() => {
autoCompleteInstanceRef.current.addListener('place_changed', onPlaceChanged);
autoCompleteInstanceRef.current.bindTo('bounds', map);
console.log(autoCompleteInstanceRef.current);
// returned function will be called on component unmount
return () => {
mapApi.event.clearInstanceListeners(searchInput!);
};
}, []);
const onPlaceChanged = () => {
console.log(autoCompleteInstanceRef.current);
const place: google.maps.places.PlaceResult = autoCompleteInstanceRef.current.getPlace();
if (!place.geometry) return;
if (place.geometry.viewport) {
map.fitBounds(place.geometry.viewport);
} else {
map.setCenter(place.geometry.location);
map.setZoom(17);
}
addplace(place);
searchInput!.blur();
};