js-api-loader
js-api-loader copied to clipboard
TypeError ?(maps-api-v3/api/js/58/8-beta/intl/nl_ALL/map)
Issue Report
Summary
We are encountering an intermittent TypeError
with the Map component in our application. The error appears to be related to the Google Maps API, specifically with the instantiation of an object in the Map library.
Error Details
-
Error Type:
TypeError
-
Error Message:
undefined is not an object (evaluating 'new Ja.OC')
-
Error Path:
(maps-api-v3/api/js/58/8-beta/intl/nl_ALL/map)
- Severity Level: Error
Environment Details
-
Google Maps API Version:
@googlemaps/js-api-loader": "1.16.8
- Browser Compatibility: Occurs across all major browsers.
-
Map Library:
@googlemaps/js-api-loader
Steps to Reproduce
This error occurs sporadically across different parts of the application where the Map component is used. There are no specific steps to reliably reproduce it, but it seems to be triggered when loading or interacting with the Map component.
Custom Map Component (React)
import { useEffect, useRef, useState, FC, ReactNode, useCallback } from 'react';
import {
Space,
useMantineTheme,
Box,
ActionIcon,
rem,
Sx,
} from '@mantine/core';
import { Loader } from '@googlemaps/js-api-loader';
import { config } from '../../config';
import Input from '../Input';
import { IconXClose } from '../../icons';
import styles from './styles';
const defaultZoom = 15;
const defaultCenter = {
lat: 52.3727,
lng: 4.8931,
};
interface CustomInputContainerProps {
children: ReactNode;
}
const AUTOCOMPLETE_OPTIONS = {
fields: ['formatted_address', 'geometry', 'name', 'place_id'],
strictBounds: false,
types: ['geocode'],
};
const ARROW_DOWN_KEY = {
bubbles: true,
cancelable: true,
key: 'Down',
code: 'Down',
keyCode: 40,
which: 40,
};
const ENTER_KEY = {
bubbles: true,
cancelable: true,
key: 'Enter',
code: 'Enter',
keyCode: 13,
which: 13,
triggered: true,
};
const Map = ({
currentLocation,
currentCoordinates,
updateInput,
inputPlaceholder,
error,
disableInput,
inputTooltipPosition,
inputTooltipTitle,
inputTooltipDescription,
CustomInputContainer,
center = defaultCenter,
zoom = defaultZoom,
isHidden,
readOnly,
mapSx = {},
noPaddings = false,
...props
}: {
currentLocation?: string | null;
currentCoordinates?: { lat: number; lng: number } | null;
updateInput?: (location: string, placeId?: string) => void;
inputPlaceholder?: string;
error?: string;
disableInput?: boolean;
inputTooltipPosition?: string;
inputTooltipTitle?: ReactNode;
inputTooltipDescription?: string | null;
center?: google.maps.LatLngLiteral;
zoom?: number;
CustomInputContainer?: FC<CustomInputContainerProps>;
isHidden?: boolean;
readOnly?: boolean;
sx?: Sx;
mapSx?: Sx;
noPaddings?: boolean;
}) => {
const theme = useMantineTheme();
const ref = useRef<HTMLDivElement>(null);
const inputRef = useRef<HTMLInputElement>(null);
const autocomplete = useRef<google.maps.places.Autocomplete | null>(null);
const marker = useRef<google.maps.marker.AdvancedMarkerElement>();
const circle = useRef<google.maps.Circle>();
const mapRef = useRef<google.maps.Map>();
const geocoder = useRef<google.maps.Geocoder>();
const markerSvg = document.createElement('div');
markerSvg.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="38" height="38" fill="none">
<path fill=${
theme.colors[theme.primaryColor][7]
} d="M19 34.833c6.333-6.333 12.667-12.004 12.667-19 0-6.995-5.671-12.666-12.667-12.666-6.996 0-12.667 5.67-12.667 12.666S12.667 28.5 19 34.833Z"/>
<path fill="#FFFFFF" d="M19 20.583a4.75 4.75 0 1 0 0-9.5 4.75 4.75 0 0 0 0 9.5Z"/>
</svg>`;
const [address, setAddress] = useState<string>(currentLocation || '');
const [coordinates, setCoordinates] = useState<
{ lat: number; lng: number } | undefined | null
>(currentCoordinates);
const [isScriptLoaded, setIsScriptLoaded] = useState(false);
const { language } = config.i18n;
const handleBlur = () => {
if (!isScriptLoaded) return;
if (!inputRef.current?.value || inputRef.current?.value.length === 0) {
if (updateInput) {
updateInput('');
}
setAddress('');
setCoordinates(null);
return;
}
// to always select the first option from the search
// because we do not have an exact list of options - simulate the choice
const arrowDownKeyEvent = new KeyboardEvent('keydown', ARROW_DOWN_KEY);
google.maps.event.trigger(inputRef.current, 'keydown', arrowDownKeyEvent);
const enterKeyEvent = new KeyboardEvent('keydown', ENTER_KEY);
google.maps.event.trigger(inputRef.current, 'keydown', enterKeyEvent);
};
const handleLocationChange = useCallback(() => {
if (
!isScriptLoaded ||
!mapRef.current ||
!google.maps.marker.AdvancedMarkerElement
)
return;
const place = autocomplete?.current?.getPlace();
if (!place?.geometry || !place?.geometry.location) {
// User entered the name of a Place that was not suggested and
// pressed the Enter key, or the Place Details request failed.
return;
}
// If the place has a geometry, then present it on a map.
if (place?.geometry.viewport) {
mapRef.current.fitBounds(place?.geometry.viewport);
} else {
mapRef.current.setCenter(place?.geometry.location);
mapRef.current.setZoom(zoom);
}
if (marker.current) {
marker.current.map = null; // Remove marker from the map
}
if (marker.current) {
marker.current = new google.maps.marker.AdvancedMarkerElement({
position: isHidden
? null
: new google.maps.LatLng(
place.geometry.location.lat(),
place.geometry.location.lng()
),
map: mapRef.current,
content: markerSvg,
});
}
circle.current?.setCenter(null);
if (updateInput) updateInput(place.formatted_address || '', place.place_id);
setAddress(place.formatted_address || '');
setCoordinates({
lng: place.geometry.location.lng(),
lat: place.geometry.location.lat(),
});
if (place.formatted_address && inputRef.current) {
inputRef.current.value = place.formatted_address;
}
}, [isScriptLoaded, updateInput, zoom]);
useEffect(() => {
const loader = new Loader({
apiKey: config.googleMapsApiKey,
language,
version: 'beta',
libraries: ['places', 'marker'],
});
loader.load().then(() => {
if (!google?.maps?.Map || !ref.current) return;
mapRef.current = new google.maps.Map(ref.current, {
center,
zoom,
styles,
fullscreenControl: false,
mapTypeControl: false,
streetViewControl: false,
keyboardShortcuts: false,
mapId: 'ofcorz_map',
});
geocoder.current = new google.maps.Geocoder();
setIsScriptLoaded(true);
});
return () => {
setIsScriptLoaded(false);
if (!window.google?.maps?.Map) return;
(Loader as any).instance = null;
loader.deleteScript();
delete (window as any).google;
marker.current = undefined;
mapRef.current = undefined;
circle.current = undefined;
};
}, [center, language, zoom]);
useEffect(() => {
if (!isScriptLoaded || !google.maps?.marker?.AdvancedMarkerElement) return;
if (!coordinates && currentCoordinates) {
setCoordinates(currentCoordinates);
}
if (currentLocation) {
if (marker.current) {
marker.current.map = null;
}
marker.current = new google.maps.marker.AdvancedMarkerElement({
position: isHidden ? null : center,
map: mapRef.current,
content: markerSvg,
});
}
circle.current = new google.maps.Circle({
map: mapRef.current,
radius: 100,
center: isHidden ? center : null,
fillColor: theme.colors[theme.primaryColor][7],
strokeWeight: 0,
});
}, [
isHidden,
center,
zoom,
currentLocation,
isScriptLoaded,
theme.colors,
theme.primaryColor,
]);
useEffect(() => {
if (
currentLocation &&
isScriptLoaded &&
geocoder.current &&
google.maps?.marker?.AdvancedMarkerElement
) {
const location =
coordinates || currentCoordinates
? { location: coordinates || currentCoordinates }
: { address: currentLocation };
geocoder.current
.geocode(location, (results, status) => {
if (results && results[0] && status === 'OK' && mapRef.current) {
mapRef.current.fitBounds(results[0]?.geometry.viewport);
mapRef.current.setCenter(results[0].geometry.location);
if (marker.current) {
marker.current.map = null; // Remove marker from the map
marker.current = new google.maps.marker.AdvancedMarkerElement({
position: isHidden
? null
: new google.maps.LatLng(
results[0].geometry.location.lat(),
results[0].geometry.location.lng()
),
map: mapRef.current,
content: markerSvg,
});
}
}
})
.catch((e) => console.log(e));
if (inputRef && inputRef.current) {
inputRef.current.value = currentLocation;
}
} else if (inputRef && inputRef.current) {
inputRef.current.value = '';
}
}, [currentLocation, isScriptLoaded, language]);
useEffect(() => {
if (
!readOnly &&
updateInput &&
isScriptLoaded &&
google.maps?.places?.Autocomplete
) {
autocomplete.current = new google.maps.places.Autocomplete(
inputRef.current as HTMLInputElement,
AUTOCOMPLETE_OPTIONS
);
if (mapRef.current) {
autocomplete.current.bindTo('bounds', mapRef.current);
}
autocomplete.current.addListener('place_changed', handleLocationChange);
}
}, [isScriptLoaded]);
const InputContainer = CustomInputContainer || Box;
return (
<Box {...props}>
<Box
sx={{ borderRadius: rem(8), ...mapSx }}
ref={ref}
id="map"
h={140}
/>
{updateInput && (
<>
{!noPaddings && <Space h="sm" />}
<InputContainer
sx={
noPaddings
? { width: '100%', marginTop: rem(10) }
: { margin: rem(16) }
}
>
<Input
defaultValue={address}
ref={inputRef}
placeholder={inputPlaceholder}
error={error}
disabled={disableInput}
tooltipPosition={inputTooltipPosition}
tooltipTitle={inputTooltipTitle}
tooltipDescription={inputTooltipDescription}
onBlur={handleBlur}
readOnly={readOnly}
customRightSection={
!readOnly && (
<ActionIcon
onClick={() => {
if (inputRef && inputRef.current) {
inputRef.current.value = '';
inputRef.current.focus();
updateInput('');
setAddress('');
setCoordinates(null);
}
}}
>
<IconXClose
width={rem(20)}
height={rem(20)}
/>
</ActionIcon>
)
}
/>
</InputContainer>
{!noPaddings && <Space h="sm" />}
</>
)}
</Box>
);
};
export default Map;
Stack Trace
TypeError ?(maps-api-v3/api/js/58/8-beta/intl/nl_ALL/map)
undefined is not an object (evaluating 'new Ja.OC')
app:///maps-api-v3/api/js/58/8-beta/intl/nl_ALL/map.js at line 123:56