React Google Map Component with Custom Markers
This code snippet showcases a React component for rendering a Google Map with custom markers, utilizing hooks for state management and context. It includes error handling and user location tracking, making it a useful component for location-based applications.unknown
typescript
5 months ago
9.4 kB
6
Indexable
'use client'; import React, { useState, useEffect, useCallback, useMemo } from 'react'; import { GoogleMap, MarkerF, InfoWindow } from '@react-google-maps/api'; import { mapStyles } from '@/lib/mapStyles'; import { useSearchContext } from '@/context/SearchContext'; import { useSearchParams } from 'next/navigation'; import useMarkerStore from '@/store/markers-store'; import { useMap } from '@/context/MapContext'; import BeeSpinner from '../BeeSpinner'; import { Button } from '@/components/ui/button'; import ModernInfoWindow from '../ModernInfoWindow'; interface Marker { id: string; name: string; address: string; lat?: number; lng?: number; referral_code: string; } interface GoogleMapViewProps { errorMessage?: string | null; } function GoogleMapView({ errorMessage }: GoogleMapViewProps) { const { isLoaded, loadError } = useMap(); const [userLocation, setUserLocation] = useState<google.maps.LatLngLiteral | null>(null); const { center, setCenter } = useSearchContext(); const { users } = useMarkerStore(); const [markers, setMarkers] = useState<Marker[]>([]); const [selectedMarker, setSelectedMarker] = useState<Marker | null>(null); const searchParams = useSearchParams(); const lat = searchParams.get('lat'); const lng = searchParams.get('lng'); const mapOptions = useMemo(() => ({ styles: mapStyles, language: 'it', region: 'IT', clickableIcons: false, zoomControl: true, streetViewControl: false, fullscreenControl: false, mapTypeControl: false, gestureHandling: 'greedy', }), []); useEffect(() => { if (lat && lng) { setCenter({ lat: parseFloat(lat), lng: parseFloat(lng) }); } else if (navigator.geolocation) { navigator.geolocation.getCurrentPosition( (position) => { const location = { lat: position.coords.latitude, lng: position.coords.longitude, }; setCenter(location); setUserLocation(location); }, (error) => { console.error("Error getting user's location:", error); } ); } }, [lat, lng, setCenter]); useEffect(() => { if (isLoaded && users.length > 0) { const geocoder = new google.maps.Geocoder(); const markersWithCoordinates = users.map((user, index) => new Promise<Marker>((resolve) => { geocoder.geocode({ address: user.fullAddress }, (results, status) => { if (status === 'OK' && results && results[0]) { resolve({ id: `${user.referral_code}_${user.address}_${user.cap}_${index}`, name: user.company_name, address: user.fullAddress, lat: results[0].geometry.location.lat(), lng: results[0].geometry.location.lng(), referral_code: user.referral_code }); } else { console.error('Geocode was not successful for the following reason:', status); resolve({ id: `${user.referral_code}_${user.address}_${user.cap}_${index}`, name: user.company_name, address: user.fullAddress, referral_code: user.referral_code }); } }); }) ); Promise.all(markersWithCoordinates).then((updatedMarkers) => { setMarkers(updatedMarkers.filter(marker => marker.lat && marker.lng)); }); } else { setMarkers([]); } }, [isLoaded, users]); const handleMarkerClick = useCallback((marker: Marker) => { setSelectedMarker(marker); }, []); const calculateDistance = useCallback((lat1: number, lon1: number, lat2: number, lon2: number) => { const R = 6371; const dLat = deg2rad(lat2 - lat1); const dLon = deg2rad(lon2 - lon1); const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(deg2rad(lat1)) * Math.cos(deg2rad(lat2)) * Math.sin(dLon / 2) * Math.sin(dLon / 2); const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); const d = R * c; return d; }, []); const deg2rad = (deg: number) => { return deg * (Math.PI / 180); }; useEffect(() => { if (isLoaded) { const removeInfoWindowPadding = () => { const infoWindows = document.querySelectorAll('.gm-style-iw-c'); infoWindows.forEach((iw) => { (iw as HTMLElement).style.padding = '0'; (iw as HTMLElement).style.maxWidth = 'none'; (iw as HTMLElement).style.maxHeight = 'none'; (iw as HTMLElement).style.overflow = 'visible'; (iw as HTMLElement).style.background = 'none'; (iw as HTMLElement).style.boxShadow = 'none'; }); const infoWindowContents = document.querySelectorAll('.gm-style-iw-d'); infoWindowContents.forEach((iwc) => { (iwc as HTMLElement).style.padding = '0'; (iwc as HTMLElement).style.maxWidth = 'none'; (iwc as HTMLElement).style.maxHeight = 'none'; (iwc as HTMLElement).style.overflow = 'visible'; }); const infoWindowBackground = document.querySelector('.gm-style-iw-t'); if (infoWindowBackground) { (infoWindowBackground as HTMLElement).style.background = 'none'; } // Remove the default close button const closeButtons = document.querySelectorAll('.gm-ui-hover-effect'); closeButtons.forEach((button) => { (button as HTMLElement).style.display = 'none'; }); // Remove the arrow const arrows = document.querySelectorAll('.gm-style-iw-tc'); arrows.forEach((arrow) => { (arrow as HTMLElement).style.display = 'none'; }); }; removeInfoWindowPadding(); const observer = new MutationObserver(removeInfoWindowPadding); observer.observe(document.body, { childList: true, subtree: true }); return () => observer.disconnect(); } }, [isLoaded, selectedMarker]); if (loadError) return <div>Errore nel caricamento di Maps. Ricarica la pagina.</div>; if (!isLoaded) return <div className='flex items-center justify-center h-[calc(100dvh-80px)] w-full'><BeeSpinner /></div>; let infoWindowOptions = {}; if (isLoaded) { infoWindowOptions = { pixelOffset: new google.maps.Size(0, -30), maxWidth: 256, disableAutoPan: false, }; } const defaultIcon = { url: '/assets/icons/Mapmarkerviola.svg', scaledSize: new google.maps.Size(50, 50), }; const selectedIcon = { url: '/assets/icons/Mapmarkergiallo.svg', scaledSize: new google.maps.Size(55, 55), }; return ( <div className="flex flex-col h-full w-full relative"> <GoogleMap mapContainerClassName="flex-1 lg:rounded-[1rem] lg:h-full rounded-none h-[calc(100vh-4rem)]" center={center} zoom={14} options={mapOptions} > {userLocation && ( <MarkerF position={userLocation} title="Your Location" /> )} {markers.map((marker) => ( marker.lat && marker.lng && ( <MarkerF key={marker.id} position={{ lat: marker.lat, lng: marker.lng }} title={`${marker.name} - ${marker.address}`} icon={selectedMarker?.id === marker.id ? selectedIcon : defaultIcon} onClick={() => handleMarkerClick(marker)} /> ) ))} {selectedMarker && selectedMarker.lat && selectedMarker.lng && ( <InfoWindow position={{ lat: selectedMarker.lat, lng: selectedMarker.lng }} onCloseClick={() => setSelectedMarker(null)} options={infoWindowOptions} > <div className="w-64 h-80 overflow-hidden rounded-lg"> <ModernInfoWindow name={selectedMarker.name} address={selectedMarker.address} onClose={() => setSelectedMarker(null)} distance={userLocation ? calculateDistance( userLocation.lat, userLocation.lng, selectedMarker.lat, selectedMarker.lng ) : null} referralCode={selectedMarker.referral_code} /> </div> </InfoWindow> )} </GoogleMap> {errorMessage && ( <div className="absolute inset-0 z-[10] flex items-center justify-center bg-black/75 backdrop-blur-sm lg:rounded-[1rem]"> <div className="bg-white p-6 rounded-lg shadow-lg max-w-md text-center"> <p className="text-red-600 font-semibold text-xl mb-2">Ops! Qualcosa è andato storto.</p> <p className="mb-4">Errore nel caricamento dei marker. Ricarica la pagina.</p> <Button variant="nodropButton" onClick={() => window.location.reload()} className="text-white px-6 py-2 rounded-[10px] transition-colors" > Ricarica la pagina </Button> </div> </div> )} </div> ); } export default GoogleMapView;
Editor is loading...
Leave a Comment