- Notifications
You must be signed in to change notification settings - Fork855
Open
Description
How to open the Heatmap on the first load on the page?
When I open the page with Heatmap, I got this errorTypeError: 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" }
Metadata
Metadata
Assignees
Labels
No labels