@emblamedical/maps
v0.3.0
Published
React components for Google Maps and Places Autocomplete
Readme
@emblamedical/maps
A React component library for integrating Google Maps, built on @googlemaps/js-api-loader and the Google Maps JavaScript API.
Installation
yarn add @emblamedical/mapsReact 17+ and react-dom are required as peer dependencies.
Table of Contents
MapsProvider
All components must be wrapped in MapsProvider, which loads the Google Maps API once and shares the loaded state via context.
import { MapsProvider } from '@emblamedical/maps';
<MapsProvider apiKey="GOOGLE_MAPS_API_KEY">
<App />
</MapsProvider>Map Component
Features
- Customizable map options.
- Smooth animated zoom-in effect on load.
- Automatic center and zoom adjustments based on props.
- Easily extensible with child components (e.g., markers, overlays).
- Optimized with React hooks for dynamic updates.
Props
| Prop | Type | Default Value | Description |
| -------------- | ------------------------------ | -------------------------- | --------------------------------------------------------------------------------------- |
| center | { lat: number, lng: number } | { lat: 0, lng: 180 } | The initial center of the map. |
| zoom | number | — | The zoom level of the map. |
| mapOptions | google.maps.MapOptions | {...defaultMapOptions}* | Additional options to customize the Google Map (merged with default options). |
| mapStyles | CSSProperties | undefined | Inline styles applied to the map container. |
| onMapLoad | () => void | undefined | Callback triggered when the map is successfully loaded. |
| zoomInOnLoad | boolean | true | If true, the map will perform an animated zoom-in effect starting from initialZoom. |
| initialZoom | number | 6 | The starting zoom level for the animated zoom-in effect when zoomInOnLoad is true. |
| children | React.ReactNode | undefined | Optional child components such as markers or overlays to render on the map. |
*defaultMapOptions
const defaultMapOptions = {
clickableIcons: false,
draggable: true,
fullscreenControl: false,
mapTypeControl: false,
minZoom: 2,
maxZoom: 18,
restriction: {
latLngBounds: {
north: 85,
south: -85,
west: -180,
east: 180,
},
strictBounds: true,
},
rotateControl: true,
scaleControl: true,
streetViewControl: false,
zoomControl: true,
};Usage
import { MapsProvider, Map, Circle, Marker, MarkerCluster, Info } from '@emblamedical/maps';
const App = () => (
<MapsProvider apiKey="GOOGLE_MAPS_API_KEY">
<div style={{ width: '100%', height: '500px' }}>
<Map
center={{ lat: 37.7749, lng: -122.4194 }}
zoom={10}
onMapLoad={() => console.log('Map loaded!')}
>
<Circle center={searchCenter} radius={circleRadius} options={circleOptions} />
<MarkerCluster
markers={locations}
renderMarker={(location) => (
<Marker
key={location.id}
position={{ lat: location.lat, lng: location.lng }}
onClick={() => setSelected(location.id)}
>
<Info show={selectedId === location.id}>
<div>{location.name}</div>
</Info>
</Marker>
)}
renderCluster={(count) => <ClusterPin count={count} />}
/>
</Map>
</div>
</MapsProvider>
);MarkerCluster Component
Groups nearby markers into cluster pins at lower zoom levels using Supercluster (KD-tree based). Clustering is disabled above maxZoom.
Props
| Prop | Type | Default | Description |
| -------------- | -------------------------------------- | ------- | ------------------------------------------------------------------ |
| markers | T[] (where T extends { lat, lng }) | — | Required. Array of marker data objects with coordinates. |
| radius | number | 40 | Cluster radius in pixels. |
| maxZoom | number | 14 | Zoom level above which clustering is disabled. |
| minPoints | number | 2 | Minimum number of markers required to form a cluster. |
| renderMarker | (point: T) => React.ReactNode | — | Required. Renders an individual marker. |
| onClusterClick | (expand: () => void) => void | — | Optional. Override click behavior. Receives the default expand (zoom-in) function as an argument. |
| renderCluster | (count: number) => React.ReactNode | — | Required. Renders a cluster pin. Clicking zooms to the cluster's expansion level by default. |
Usage
import { MarkerCluster, Marker } from '@emblamedical/maps';
<MarkerCluster
markers={locations}
renderMarker={(location) => (
<Marker key={location.id} position={{ lat: location.lat, lng: location.lng }}>
<MapPin />
</Marker>
)}
renderCluster={(count) => <ClusterPin count={count} />}
/>Autocomplete Component
Provides location-based search using Google Maps Autocomplete.
Props
| Prop | Type | Default | Description |
| -------------------- | --------------------------------------------- | ------------- | ------------------------------------------------------------------------------------------- |
| onGetPlace | (place: Place) => void | — | Required. Callback with the selected place data. |
| customOptions | google.maps.places.AutocompleteOptions | null | Custom options to configure Google Maps Autocomplete. |
| countryRestriction | string | null | Restrict results to a country using ISO 3166-1 Alpha-2 codes. |
| customTypes | string[] | ['geocode'] | Types of predictions to return. |
| inputRef | React.Ref<HTMLInputElement> | undefined | Ref for the input field. |
| placeholder | string | undefined | Placeholder text. |
| ...props | React.InputHTMLAttributes<HTMLInputElement> | — | Additional props passed to the input element. |
Usage
import { AutoComplete, CurrentLocation } from '@emblamedical/maps';
const Search = ({ onGetValue, inputRegion }) => {
const inputRef = useRef<HTMLInputElement>(null);
return (
<div>
<AutoComplete
inputRef={inputRef}
placeholder="Enter a location"
onGetPlace={onGetValue}
countryRestriction="us"
/>
<CurrentLocation onGetLocation={onGetValue}>
{(isSearching) =>
isSearching ? <button disabled>Loading...</button> : <button>Use my location</button>
}
</CurrentLocation>
</div>
);
};CurrentLocation Component
Retrieves the user's current location via the browser Geolocation API and reverse-geocodes it. Uses a render prop to expose the loading state.
Props
| Prop | Type | Description |
| --------------- | ---------------------------------------------------------- | --------------------------------------------------------------- |
| onGetLocation | (place: Place) => void | Required. Callback with the resolved place object. |
| children | (isSearchingCurrentLocation: boolean) => React.ReactNode | Required. Render prop based on the loading state. |
Return shape
Success
{
"lat": 37.7749,
"lng": -122.4194,
"formatted_address": "San Francisco, CA, USA"
}Error
{
"errorType": "noResult",
"error": "User denied geolocation prompt"
}