import * as React from 'react';
import PropTypes from 'prop-types';
import Box from '@mui/material/Box';
import TextField from '@mui/material/TextField';
import Autocomplete from '@mui/material/Autocomplete';
import Paper from '@mui/material/Paper';
import LocationOnIcon from '@mui/icons-material/LocationOn';
import Grid from '@mui/material/Grid';
import Typography from '@mui/material/Typography';
import parse from 'autosuggest-highlight/parse';
// For the sake of this demo, we have to use debounce to reduce Google Maps Places API quote use
// But prefer to use throttle in practice
// import throttle from 'lodash/throttle';
import { debounce } from '@mui/material/utils';
// This key was created specifically for the demo in mui.com.
// You need to create a new one for your application.
const GOOGLE_MAPS_API_KEY = 'AIzaSyC3aviU6KHXAjoSnxcw6qbOhjnFctbxPkE';
const useEnhancedEffect =
typeof window !== 'undefined' ? React.useLayoutEffect : React.useEffect;
function loadScript(src, position) {
const script = document.createElement('script');
script.setAttribute('async', '');
script.src = src;
position.appendChild(script);
return script;
}
function CustomPaper(props) {
return (
{props.children}
{/* Legal requirement https://developers.google.com/maps/documentation/javascript/policies#logo */}
({
display: 'flex',
justifyContent: 'flex-end',
p: '5px 10px 6px 10px',
opacity: 0.9,
'& path': {
fill: '#5e5e5e',
},
...staticTheme.applyStyles('dark', {
opacity: 0.7,
'& path': {
fill: '#fff',
},
}),
})}
>
);
}
CustomPaper.propTypes = {
/**
* The content of the component.
*/
children: PropTypes.node,
};
const fetch = debounce(async (request, callback) => {
try {
const { suggestions } =
await window.google.maps.places.AutocompleteSuggestion.fetchAutocompleteSuggestions(
request,
);
callback(
suggestions.map((suggestion) => {
const place = suggestion.placePrediction;
// Map to the old AutocompleteService.getPlacePredictions format
// https://developers.google.com/maps/documentation/javascript/places-migration-autocomplete
return {
description: place.text.text,
structured_formatting: {
main_text: place.mainText.text,
main_text_matched_substrings: place.mainText.matches.map((match) => ({
offset: match.startOffset,
length: match.endOffset - match.startOffset,
})),
secondary_text: place.secondaryText?.text,
},
};
}),
);
} catch (err) {
if (err.message.startsWith('Quota exceeded for quota')) {
callback(request.input.length === 1 ? fakeAnswer.p : fakeAnswer.paris);
}
throw err;
}
}, 400);
const emptyOptions = [];
let sessionToken;
export default function GoogleMaps() {
const [value, setValue] = React.useState(null);
const [inputValue, setInputValue] = React.useState('');
const [options, setOptions] = React.useState(emptyOptions);
const callbackId = React.useId().replace(/[^\w]/g, '');
const [loaded, setLoaded] = React.useState(false);
if (typeof window !== 'undefined') {
if (!document.querySelector('#google-maps')) {
const GOOGLE_NAMESPACE = '_google_callback';
const globalContext =
window[GOOGLE_NAMESPACE] || (window[GOOGLE_NAMESPACE] = {});
globalContext[callbackId] = () => {
setLoaded(true);
};
const script = loadScript(
`https://maps.googleapis.com/maps/api/js?key=${GOOGLE_MAPS_API_KEY}&libraries=places&loading=async&callback=${GOOGLE_NAMESPACE}.${callbackId}`,
document.querySelector('head'),
);
script.id = 'google-maps';
} else if (window.google && !loaded) {
setLoaded(true);
}
}
useEnhancedEffect(() => {
if (!loaded) {
return undefined;
}
if (inputValue === '') {
setOptions(value ? [value] : emptyOptions);
return undefined;
}
// Allow to resolve the out of order request resolution.
let active = true;
if (!sessionToken) {
sessionToken = new window.google.maps.places.AutocompleteSessionToken();
}
fetch({ input: inputValue, sessionToken }, (results) => {
if (!active) {
return;
}
let newOptions = [];
if (results) {
newOptions = results;
if (value) {
newOptions = [
value,
...results.filter((result) => result.description !== value.description),
];
}
} else if (value) {
newOptions = [value];
}
setOptions(newOptions);
});
return () => {
active = false;
};
}, [value, inputValue, loaded]);
return (
typeof option === 'string' ? option : option.description
}
filterOptions={(x) => x}
slots={{
paper: CustomPaper,
}}
options={options}
autoComplete
includeInputInList
filterSelectedOptions
value={value}
noOptionsText="No locations"
onChange={(event, newValue) => {
setOptions(newValue ? [newValue, ...options] : options);
setValue(newValue);
}}
onInputChange={(event, newInputValue) => {
setInputValue(newInputValue);
}}
renderInput={(params) => (
)}
renderOption={(props, option) => {
const { key, ...optionProps } = props;
const matches = option.structured_formatting.main_text_matched_substrings;
const parts = parse(
option.structured_formatting.main_text,
matches.map((match) => [match.offset, match.offset + match.length]),
);
return (
{parts.map((part, index) => (
{part.text}
))}
{option.structured_formatting.secondary_text ? (
{option.structured_formatting.secondary_text}
) : null}
);
}}
/>
);
}
// Fake data in case Google Maps Places API returns a rate limit.
const fakeAnswer = {
p: [
{
description: 'Portugal',
structured_formatting: {
main_text: 'Portugal',
main_text_matched_substrings: [{ offset: 0, length: 1 }],
},
},
{
description: 'Puerto Rico',
structured_formatting: {
main_text: 'Puerto Rico',
main_text_matched_substrings: [{ offset: 0, length: 1 }],
},
},
{
description: 'Pakistan',
structured_formatting: {
main_text: 'Pakistan',
main_text_matched_substrings: [{ offset: 0, length: 1 }],
},
},
{
description: 'Philippines',
structured_formatting: {
main_text: 'Philippines',
main_text_matched_substrings: [{ offset: 0, length: 1 }],
},
},
{
description: 'Paris, France',
structured_formatting: {
main_text: 'Paris',
main_text_matched_substrings: [{ offset: 0, length: 1 }],
secondary_text: 'France',
},
},
],
paris: [
{
description: 'Paris, France',
structured_formatting: {
main_text: 'Paris',
main_text_matched_substrings: [{ offset: 0, length: 5 }],
secondary_text: 'France',
},
},
{
description: 'Paris, TX, USA',
structured_formatting: {
main_text: 'Paris',
main_text_matched_substrings: [{ offset: 0, length: 5 }],
secondary_text: 'TX, USA',
},
},
{
description: "Paris Beauvais Airport, Route de l'Aéroport, Tillé, France",
structured_formatting: {
main_text: 'Paris Beauvais Airport',
main_text_matched_substrings: [{ offset: 0, length: 5 }],
secondary_text: "Route de l'Aéroport, Tillé, France",
},
},
{
description: 'Paris Las Vegas, South Las Vegas Boulevard, Las Vegas, NV, USA',
structured_formatting: {
main_text: 'Paris Las Vegas',
main_text_matched_substrings: [{ offset: 0, length: 5 }],
secondary_text: 'South Las Vegas Boulevard, Las Vegas, NV, USA',
},
},
{
description: "Paris La Défense Arena, Jardin de l'Arche, Nanterre, France",
structured_formatting: {
main_text: 'Paris La Défense Arena',
main_text_matched_substrings: [{ offset: 0, length: 5 }],
secondary_text: "Jardin de l'Arche, Nanterre, France",
},
},
],
};