How to open the Heatmap on the first load on the page?
When I open the page with Heatmap, I got this error
TypeError: Cannot read properties of undefined (reading 'HeatmapLayer')
After reading the page, Heatmap appears and seems to be working as it should
Components:
CommunityMap.tsx
export const CommunityMap: React.FC<Props> = ({ expandMap = false, ...community }) => {
const [page] = useQueryState('tab', 'here');
const [tripId] = useQueryState('trip');
const router = useRouter();
const { isMobile } = React.useContext(LayoutContext);
const [selectedDate, setSelectedDate] = React.useState<number>(0);
// Generate the array of dates for the next 30 days
const dates = Array.from({ length: 30 }, (_, i) => addDays(new Date(), i));
// Get the display label for the marks
// const marks = dates.map((date, index) => ({
// value: index,
// label: format(date, 'MMM dd'),
// }));
const handleSliderChange = React.useCallback((event: any, newValue: any) => {
setSelectedDate(newValue);
}, []);
const { user } = React.useContext(UserContext);
const { openDialog } = React.useContext(DialogContext);
const [showHeatmap, setShowHeatmap] = React.useState(false);
const { trips } = useTrips({
user: user?.id,
});
const currTripIdx = React.useMemo(() => {
if (tripId) {
return findIndex(trips, trip => trip.id === parseInt(tripId, 10));
}
}, [trips, tripId]);
const currTrip = React.useMemo(() => {
if (currTripIdx !== undefined) {
return trips[currTripIdx];
}
}, [trips, currTripIdx]);
const prevTrip = React.useMemo(() => {
if (currTripIdx !== undefined) {
return trips[currTripIdx - 1];
}
}, [trips, currTripIdx]);
const nextTrip = React.useMemo(() => {
if (currTripIdx !== undefined) {
return trips[currTripIdx + 1];
}
}, [trips, currTripIdx]);
const { members: communityMembers, status: communityMembersStatus } = useMembers({
communityId: community.id.toString(),
dateGte: showHeatmap ? startOfDay(dates[selectedDate]) : currTrip?.arrivalDatetime,
dateLte: showHeatmap ? endOfDay(dates[selectedDate]) : currTrip?.departureDatetime,
});
const showHeatToggle = React.useMemo(() => {
if (isBeta || (user?.id && [13, 15, 241, 223, 11, 5, 166, 1781].includes(user.id))) {
return true;
}
return false;
}, [user]);
const members = React.useMemo(() => {
return communityMembers.filter(user => user.status === 'active');
}, [communityMembers]);
const [map, setMap] = React.useState<any>();
const coords: Coordinates = React.useMemo(() => {
const boundaries = members.reduce((prev, curr) => {
const bounds = { ...prev };
// @ts-ignore
const userLocation = curr.trip?.location || curr.baseLocation || curr.location;
if (userLocation && userLocation.latitude && userLocation.longitude) {
if (userLocation.latitude < bounds.minLat) {
bounds.minLat = userLocation.latitude;
}
if (userLocation.latitude > bounds.maxLat) {
bounds.maxLat = userLocation.latitude;
}
if (userLocation.longitude < bounds.minLng) {
bounds.minLng = userLocation.longitude;
}
if (userLocation.longitude > bounds.maxLng) {
bounds.maxLng = userLocation.longitude;
}
}
return bounds;
}, {
minLat: user?.baseLocation?.latitude || 0,
minLng: user?.baseLocation?.longitude || 0,
maxLat: user?.baseLocation?.latitude || 0,
maxLng: user?.baseLocation?.longitude || 0,
});
const distance = calculateDistance(boundaries.minLat, boundaries.minLng, boundaries.maxLat, boundaries.maxLng);
const coords = currTrip ? {
lat: currTrip.location.latitude,
lng: currTrip.location.longitude,
} : {
// If all users located too close to each other (less than 100 km) - increase zoom out distance
minLat: distance < 100 ? boundaries.minLat - 0.4 : boundaries.minLat,
minLng: distance < 100 ? boundaries.minLng - 0.4 : boundaries.minLng,
maxLat: distance < 100 ? boundaries.maxLat + 0.4 : boundaries.maxLat,
maxLng: distance < 100 ? boundaries.maxLng + 0.4 : boundaries.maxLng,
lat: (boundaries.minLat + boundaries.maxLat) / 2,
lng: (boundaries.minLng + boundaries.maxLng) / 2,
};
return coords;
}, [currTrip, members, user?.baseLocation]);
const points = React.useMemo(() => {
const points: Locations.Location[] = [];
const dateFrom = currTrip?.arrivalDatetime;
const dateTo = currTrip?.departureDatetime;
members.forEach(user => {
const travelInterval = dateFrom && dateTo ? { start: dateFrom, end: dateTo } : null;
let daysWithoutTravel = travelInterval ? eachDayOfInterval(travelInterval).length : 1;
if (!travelInterval && user.trips.length > 0) {
daysWithoutTravel = 0;
}
user.trips.forEach(trip => {
points.push(trip.location);
if (travelInterval) {
const overlappingDays = getOverlappingDaysInIntervals(
travelInterval,
{ start: trip.arrivalDatetime, end: trip.departureDatetime },
);
daysWithoutTravel -= overlappingDays;
}
});
const baseLocation = user.baseLocation;
if (baseLocation && daysWithoutTravel) {
points.push(baseLocation);
}
});
return points;
}, [members, currTrip]);
const topCities = React.useMemo(() => {
const cityCounts = points.reduce((acc, location) => {
const city = location.title;
if (city) {
if (!acc[city]) {
acc[city] = 0;
}
acc[city]++;
}
return acc;
}, {} as any);
const sortedCities = Object.entries(cityCounts).sort((a: any, b: any) => b[1] - a[1]);
return sortedCities.slice(0, 5).map(([city, count]) => ({
city,
count,
percentage: ((count as number / points.length) * 100).toFixed(0),
}));
}, [points]);
const setView = React.useCallback((tab: string) => {
if (tab === 'here') {
router.communities.view(community.handle || community.id).go({ tab: 'here', trip: undefined, lat: undefined, lng: undefined }, true);
}
if (tab === 'there') {
router.communities.view(community.handle || community.id).go({ tab: 'there', trip: trips[0].id, lat: undefined, lng: undefined }, true);
}
}, [community.handle, community.id, router.communities, trips]);
const toggleHeatMap = React.useCallback(() => {
setShowHeatmap(state => !state);
}, []);
React.useEffect(() => {
if (!page) {
router.communities.view(community.handle || community.id).go({ tab: 'here' }, true);
}
}, [community.handle, community.id, page, router]);
React.useEffect(() => {
if (!expandMap) {
if (page !== 'here') {
setView('here');
}
if (showHeatmap) {
setShowHeatmap(false);
}
}
}, [expandMap, setView, setShowHeatmap, page, showHeatmap]);
React.useEffect(() => {
if (page === 'here' && isBoundaryCoordinates(coords)) {
setTimeout(() => {
map?.fitBounds({
north: coords.maxLat,
east: coords.maxLng,
south: coords.minLat,
west: coords.minLng,
});
}, 500);
}
}, [expandMap, map, coords, page]);
if (communityMembersStatus === 'error' || !communityMembers) {
return <MessageFeedbackView height="100%"/>;
}
return (
<>
{communityMembersStatus === 'loading' && (
<Box zIndex={1} position="absolute" sx={theme => ({ opacity: 0.8, backgroundColor: theme.palette.mode === 'light' ? theme.palette.grey[100] : theme.palette.primary.dark })} height="100%" width="100%">
<BodyLoading height={700} />
</Box>
)}
<Stack height="100%">
<Box height="100%">
{showHeatmap ? (
<UsersHeatmap
coords={coords}
users={members}
// contacts={filteredContacts}
draggable={expandMap}
// We have expanding map
// By default map will not raise onChange event if parent size has changed
// To change such behavior add resetBoundsOnResize = {true} property
resetBoundsOnResize
onGoogleApiLoaded={(data) => {
setMap(data.map);
}}
dateFrom={currTrip?.arrivalDatetime}
dateTo={currTrip?.departureDatetime}
/>
) : (
<UsersMap
coords={coords}
users={members}
// contacts={filteredContacts}
draggable={expandMap}
// We have expanding map
// By default map will not raise onChange event if parent size has changed
// To change such behavior add resetBoundsOnResize = {true} property
resetBoundsOnResize
onGoogleApiLoaded={(data) => {
setMap(data.map);
}}
dateFrom={currTrip?.arrivalDatetime}
dateTo={currTrip?.departureDatetime}
/>
)}
</Box>
</Stack>
</>
);
};
'@modules/Users/components/UsersMap/HeatMap'
import React from 'react';
import { eachDayOfInterval, getOverlappingDaysInIntervals } from 'date-fns';
import { ChangeEventValue, Props as GoogleMapProps } from 'google-map-react';
import { Map } from '@shared/components/Map/Map';
import { googleMapsApiKey } from '@shared/config';
import { ClusterPoint } from './ClusterPin';
export const isBoundaryCoordinates = (coords: Coordinates): coords is BoundaryCoordinates => {
return ('minLat' in coords) && !!coords.minLat && !!coords.minLng && !!coords.maxLat && !!coords.maxLng;
};
export type SimpleCoordinates = {
lat: number;
lng: number;
}
export type BoundaryCoordinates = {
lat: number;
lng: number;
minLat: number;
minLng: number;
maxLat: number;
maxLng: number;
}
export type Coordinates = SimpleCoordinates | BoundaryCoordinates;
interface Props extends Omit<GoogleMapProps, 'heatmap' | 'heatmapLibrary'> {
coords: Coordinates;
defaultZoom?: number;
draggable?: boolean;
users: Users.User[],
contacts?: Contacts.Contact[],
onChange?: (values: ChangeEventValue) => void;
dateFrom?: Date;
dateTo?: Date;
}
export const UsersHeatmap: React.FC<Props> = ({
coords,
defaultZoom = 7,
draggable = true,
users,
contacts = [],
onChange,
dateFrom,
dateTo,
...props
}) => {
const [map, setMap] = React.useState<any>();
const [zoom, setZoom] = React.useState<number>(defaultZoom);
const clusterPoints = React.useMemo(() => {
const clusterPoints: ClusterPoint[] = [];
users.forEach(user => {
const travelInterval = dateFrom && dateTo ? { start: dateFrom, end: dateTo } : null;
let daysWithoutTravel = travelInterval ? eachDayOfInterval(travelInterval).length : 1;
// User has a trip but there is no travel interval, meaning he has a trip today.
if (!travelInterval && user.trips.length > 0) {
daysWithoutTravel = 0;
}
user.trips.forEach(trip => {
clusterPoints.push({
type: 'Feature',
properties: {
cluster: false,
user: {
...user,
trips: [trip],
},
},
geometry: {
type: 'Point',
coordinates: [trip.location.longitude, trip.location.latitude],
},
});
if (travelInterval) {
const overlappingDays = getOverlappingDaysInIntervals(
travelInterval,
{ start: trip.arrivalDatetime, end: trip.departureDatetime },
);
daysWithoutTravel -= overlappingDays;
}
});
const baseLocation = user.baseLocation;
if (baseLocation && daysWithoutTravel) {
clusterPoints.push({
type: 'Feature',
properties: {
cluster: false,
user: {
...user,
trips: [],
},
},
geometry: {
type: 'Point',
coordinates: [baseLocation?.longitude, baseLocation?.latitude],
},
});
}
});
contacts.forEach(contact => {
const location = contact.location;
if (location) {
clusterPoints.push({
type: 'Feature',
properties: { cluster: false, contact },
geometry: {
type: 'Point',
coordinates: [location?.longitude, location?.latitude],
},
});
}
});
return clusterPoints;
}, [users, contacts, dateFrom, dateTo]);
const onMapChange = React.useCallback((props: ChangeEventValue) => {
const { zoom } = props;
setZoom(zoom);
onChange && onChange(props);
}, [onChange]);
React.useEffect(() => {
if (isBoundaryCoordinates(coords)) {
map?.fitBounds({
north: coords.maxLat,
east: coords.maxLng,
south: coords.minLat,
west: coords.minLng,
});
}
}, [users, coords, map]);
return (
<Map
{...props}
options={{ zoomControl: false }}
lat={coords.lat}
lng={coords.lng}
defaultZoom={defaultZoom}
zoom={zoom}
center={{ lat: coords.lat, lng: coords.lng }}
onChange={onMapChange}
draggable={draggable}
onGoogleApiLoaded={(data) => {
setMap(data.map);
props.onGoogleApiLoaded && props.onGoogleApiLoaded(data);
}}
bootstrapURLKeys={{
key: googleMapsApiKey,
// https://stackoverflow.com/a/27054207/12347085
libraries: ['visualization'],
}}
heatmap={{
positions: clusterPoints.map(point => ({
lat: point.geometry.coordinates[1],
lng: point.geometry.coordinates[0],
})),
options: {
radius: 30,
opacity: 0.6,
},
}}
/>
);
};
'@shared/components/Map/Map'
import React, { ComponentClass, FC, ReactElement } from 'react';
import { useTheme } from '@mui/material';
import GoogleMap, { Props as GoogleMapProps } from 'google-map-react';
import noop from 'lodash/noop';
import { googleMapsApiKey } from '@shared/config';
import { dark } from './Map.dark.styles';
import { light } from './Map.light.styles';
const ReactGoogleMap = GoogleMap as ComponentClass<GoogleMapProps>;
interface MarkerProps {
lat: number,
lng: number,
children: ReactElement<any, any>,
}
export const Marker: FC<MarkerProps> = ({ children }) => children;
export const Map: FC<{ lat: number, lng: number } & GoogleMapProps> = ({
lat,
lng,
children,
defaultZoom = 14,
onChange = noop,
...props
}) => {
const theme = useTheme();
const styles = theme.palette.mode === 'dark' ? dark : light;
return (
<ReactGoogleMap
bootstrapURLKeys={{ key: googleMapsApiKey }}
center={{ lat, lng }}
defaultCenter={props.defaultCenter}
defaultZoom={defaultZoom}
{...props}
onChange={onChange}
options={{
styles,
fullscreenControl: false,
...props.options,
}}
yesIWantToUseGoogleMapApiInternals
>
{children}
</ReactGoogleMap>
);
};
**Screenshots 🖥**

Environment:
"dependencies": {
"@byteowls/capacitor-sms": "5.0.0",
"@capacitor-community/contacts": "5.0.5",
"@capacitor-community/fcm": "6.0.0",
"@capacitor-firebase/authentication": "6.0.0",
"@capacitor/android": "6.1.0",
"@capacitor/app": "6.0.0",
"@capacitor/browser": "6.0.1",
"@capacitor/clipboard": "^6.0.1",
"@capacitor/core": "6.1.0",
"@capacitor/geolocation": "6.0.0",
"@capacitor/ios": "6.1.0",
"@capacitor/keyboard": "6.0.1",
"@capacitor/push-notifications": "6.0.1",
"@capacitor/share": "6.0.1",
"@capawesome/capacitor-badge": "6.0.0",
"@emotion/react": "11.11.3",
"@emotion/styled": "11.11.0",
"@hotjar/browser": "1.0.9",
"@mui/icons-material": "5.16.5",
"@mui/lab": "5.0.0-alpha.122",
"@mui/material": "5.11.12",
"@mui/x-date-pickers-pro": "7.11.1",
"@react-spring/web": "^9.7.4",
"@sandstreamdev/react-swipeable-list": "^1.0.2",
"@sentry/react": "7.102.0",
"@sentry/vite-plugin": "2.16.1",
"@talkjs/react": "0.1.7",
"@tanstack/react-query": "4.26.1",
"@tanstack/react-virtual": "3.0.1",
"@types/google-libphonenumber": "^7.4.30",
"@types/google-map-react": "^2.1.10",
"axios": "1.3.4",
"capacitor-native-settings": "6.0.0",
"capacitor-plugin-app-tracking-transparency": "2.0.5",
"chalk": "4.1.2",
"clear": "0.1.0",
"date-fns": "2.29.3",
"date-fns-tz": "2.0.1",
"figlet": "1.5.2",
"firebase": "9.17.2",
"framer-motion": "^11.3.30",
"geojson": "0.5.0",
"google-libphonenumber": "^3.2.34",
"google-map-react": "^2.2.1",
"inquirer": "8.0.0",
"jsdom": "21.1.1",
"lodash": "4.17.21",
"query-string": "8.1.0",
"react": "18.3.1",
"react-device-detect": "2.2.3",
"react-dom": "18.3.1",
"react-facebook-pixel": "1.0.4",
"react-firebase-hooks": "5.1.1",
"react-hook-form": "7.43.5",
"react-international-phone": "^4.2.9",
"react-markdown": "9.0.1",
"react-qr-code": "^2.0.15",
"react-router": "6.9.0",
"react-router-dom": "6.9.0",
"react-spinners": "0.13.8",
"react-text-loop": "^2.3.0",
"react-tinder-card": "^1.6.4",
"react-use": "17.4.0",
"rimraf": "4.4.0",
"sanitize-html": "2.10.0",
"supercluster": "8.0.1",
"talkjs": "0.18.0",
"use-supercluster": "1.1.0",
"web-vitals": "3.3.0"
}
How to open the Heatmap on the first load on the page?
When I open the page with Heatmap, I got this error
TypeError: Cannot read properties of undefined (reading 'HeatmapLayer')After reading the page, Heatmap appears and seems to be working as it should
Components:
CommunityMap.tsx'@modules/Users/components/UsersMap/HeatMap''@shared/components/Map/Map'**Screenshots 🖥** 
Environment: