Map
Compound map on react-native-maps with markers, callouts, circles, polylines, polygons, marker clustering, and map-type, zoom, locate and search controls. Falls back to a card when the module is missing.
Installation
pnpm dlx @docyrus/cli add @docyrus/rn-mappnpm add react-native-maps @react-native-community/geolocation (optional)react-native-maps is an optional peer. It is loaded lazily. If it is not installed, <Map> renders a fallback card (or your fallback), and every layer and control renders nothing. The module works in Expo Go. For a development build, the Android Google Maps SDK needs an API key: set android.config.googleMaps.apiKey in app.config.ts, or use the react-native-maps config plugin. To use provider="google" on iOS you also need ios.config.googleMapsApiKey.
MapLocateControl uses the optional @react-native-community/geolocation peer. Without it, the control falls back to the map's own user-location updates (showsUserLocation). If neither is available, the control hides itself.
Usage
import { useRef, useState } from 'react';
import { Text, View } from 'react-native';
import {
Map,
MapCallout,
MapCircle,
MapLocateControl,
MapMarker,
MapMarkerClusterGroup,
MapSearchAreaControl,
MapTypeControl,
MapZoomControl,
regionFromCenter,
type MapBounds,
type MapHandle
} from '@/components/docyrus-native/map';
const center = { latitude: 41.0082, longitude: 28.9784 };
export function StoresMap({ stores }: { stores: Array<{ id: string; name: string; latitude: number; longitude: number }> }) {
const mapRef = useRef<MapHandle>(null);
const [bounds, setBounds] = useState<MapBounds | null>(null);
const [applied, setApplied] = useState<MapBounds | null>(null);
return (
<Map
ref={mapRef}
initialRegion={regionFromCenter(center, 11)}
fitToCoordinates={stores}
onRegionChangeComplete={(_, details) => setBounds(details.bounds)}
onLongPress={coordinate => console.log('drop pin', coordinate)}
className="h-[360px]">
<MapMarkerClusterGroup>
{stores.map(store => (
<MapMarker key={store.id} id={store.id} coordinate={store}>
<View className="rounded-full bg-primary px-2 py-1">
<Text className="text-[11px] font-semibold text-primary-foreground">{store.name}</Text>
</View>
<MapCallout>
<Text className="font-semibold text-popover-foreground">{store.name}</Text>
</MapCallout>
</MapMarker>
))}
</MapMarkerClusterGroup>
<MapCircle center={center} radius={1500} />
<MapZoomControl />
<MapTypeControl />
<MapLocateControl />
<MapSearchAreaControl
visible={JSON.stringify(bounds) !== JSON.stringify(applied)}
onPress={() => setApplied(bounds)} />
</Map>
);
}<Map> sorts its direct children. Controls (MapZoomControl, MapLocateControl, MapTypeControl, MapSearchControl, MapSearchAreaControl, MapControlContainer, and anything wrapped with markMapControl()) render in an overlay above the map. Everything else renders on the native map view. Fragments and arrays are flattened.
Legacy convenience props
The previous single-component API still works:
<Map
latitude={41.0082}
longitude={28.9784}
zoom={12}
markers={[{ latitude: 41.0082, longitude: 28.9784, title: 'Istanbul' }]}
onMarkerPress={(index, marker) => console.log(index, marker.title)}
className="h-[240px]" />Changing latitude, longitude or zoom animates the camera. This applies only when neither region nor initialRegion is set.
Driving it from a data hook
The API is shaped so that a map-view hook (the web useDocyrusMapView) can drive it:
| Hook concept | Map API |
|---|---|
markers[] with image / icon | MapMarker with a custom child view (<Image> / DocyrusIcon) and tracksViewChanges={false} once loaded |
basemap id (street, satellite, hybrid, terrain) | mapType (standard, satellite, hybrid, terrain) |
| viewport bounds | onRegionChangeComplete(region, { bounds, zoom }) |
"search this area" / isViewportStale | MapSearchAreaControl visible onPress |
onMarkerMove / handleMarkerMove | MapMarker draggable onDragEnd(coordinate, id) |
onMapClick | onPress / onLongPress |
center / bounds / fit | fitToCoordinates prop or ref.fitToCoordinates() |
API Reference
Map
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | — | Map layers and controls. |
ref | Ref<MapHandle> | — | Imperative handle (React 19 ref-as-prop). See MapHandle. |
region | MapRegion | — | Controlled region. Update it from onRegionChangeComplete. |
initialRegion | MapRegion | — | Uncontrolled initial region. |
onRegionChange | (region: MapRegion) => void | — | Fired continuously while the camera moves. |
onRegionChangeComplete | (region: MapRegion, details: MapRegionChangeDetails) => void | — | Fired when the camera settles. details carries bounds, zoom and isGesture. |
fitToCoordinates | MapLatLng[] | — | Fits the viewport to these coordinates whenever the list changes (after the map is ready). |
fitPadding | number | Partial<MapEdgePadding> | 48 | Edge padding used by fitToCoordinates (the prop and the handle). |
singlePointZoom | number | 14 | Zoom used when fitting a single coordinate. |
mapType | 'standard' | 'satellite' | 'hybrid' | 'terrain' | — | Controlled map type. terrain needs the Google provider. |
defaultMapType | MapType | 'standard' | Initial map type when uncontrolled. |
onMapTypeChange | (mapType: MapType) => void | — | Fired by MapTypeControl and by useMap().setMapType. |
provider | 'default' | 'google' | 'default' | 'google' forces Google Maps on iOS. |
showsUserLocation | boolean | false | Show the native user-location dot. |
showsMyLocationButton | boolean | false | Platform my-location button (Android Google Maps). |
showsCompass | boolean | true | Show the compass. |
showsScale | boolean | false | Show the scale bar. |
showsBuildings | boolean | true | Show 3D buildings. |
showsTraffic | boolean | false | Show traffic. |
showsPointsOfInterest | boolean | true | Show points of interest. |
zoomEnabled | boolean | true | Allow pinch zoom. |
scrollEnabled | boolean | true | Allow panning. |
rotateEnabled | boolean | true | Allow rotation. |
pitchEnabled | boolean | true | Allow pitch. |
minZoomLevel | number | — | Minimum zoom level. |
maxZoomLevel | number | — | Maximum zoom level. |
onPress | (coordinate: MapLatLng) => void | — | Tap on the map (marker taps are filtered out). |
onLongPress | (coordinate: MapLatLng) => void | — | Long-press on the map, for example to drop a pin. |
onMapReady | () => void | — | The native map finished loading. |
fallback | ReactNode | — | Rendered instead of the default card when react-native-maps is missing. |
className | string | — | Container classes. Give the map a height (h-[320px], flex-1). |
latitude | number | 0 | Legacy. Center latitude. |
longitude | number | 0 | Legacy. Center longitude. |
zoom | number | 10 | Legacy. Zoom level. |
markers | MapMarkerItem[] | — | Legacy. Plain markers with a default callout. |
onMarkerPress | (index: number, marker: MapMarkerItem) => void | — | Legacy. Press on a markers entry. |
MapHandle
| Method | Signature | Description |
|---|---|---|
animateToRegion | (region: MapRegion, duration?: number) => void | Animates to a region (350 ms by default). |
animateCamera | (camera: Partial<MapCamera>, duration?: number) => void | Animates heading, pitch, center or zoom. |
fitToCoordinates | (coordinates: MapLatLng[], options?: MapFitOptions) => void | Fits the viewport. A single coordinate centers on it at singlePointZoom. |
zoomIn / zoomOut | () => void | Zooms one level. Region-based, so it works on Apple and Google maps. |
setZoom | (zoom: number) => void | Absolute zoom that keeps the current center. |
getRegion | () => MapRegion | null | Last settled region. |
getBounds | () => MapBounds | null | Last settled bounds. |
getCamera | () => Promise<MapCamera | null> | Native camera. |
MapMarker
| Prop | Type | Default | Description |
|---|---|---|---|
coordinate | MapLatLng | — | Marker position. Required. |
id | string | — | Stable id. Passed to the press and drag callbacks. |
title | string | — | Default-callout title. |
description | string | — | Default-callout description. |
children | ReactNode | — | Custom marker view and/or a MapCallout. |
image | ImageSourcePropType | — | Bitmap marker image (faster than a custom view). |
pinColor | string | — | Tint of the default pin. |
anchor | { x: number; y: number } | — | Anchor point of the marker view, in [0, 1] space. |
calloutAnchor | { x: number; y: number } | — | Anchor point of the callout. |
tracksViewChanges | boolean | platform default (true) | Re-render the custom view. Set false once it is static, for performance. |
draggable | boolean | — | Long-press to drag. |
onDragStart | (coordinate: MapLatLng, id?: string) => void | — | Drag started. |
onDrag | (coordinate: MapLatLng, id?: string) => void | — | While dragging. |
onDragEnd | (coordinate: MapLatLng, id?: string) => void | — | Drag ended with the new coordinate. |
onPress | (coordinate: MapLatLng, id?: string) => void | — | Marker press. |
onCalloutPress | () => void | — | Press on the callout. |
opacity | number | — | Marker opacity. |
flat | boolean | — | Flat against the map (rotates with it). |
rotation | number | — | Rotation in degrees. |
zIndex | number | — | Stacking order. |
stopPropagation | boolean | — | Keep the press from reaching the map's onPress. |
MapCallout (alias MapPopup)
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | — | Callout content. |
tooltip | boolean | true | true renders a themed popover bubble. false uses the native callout bubble. |
onPress | () => void | — | Press on the callout. |
className | string | — | Classes for the content wrapper. |
MapCircle
| Prop | Type | Default | Description |
|---|---|---|---|
center | MapLatLng | — | Center. Required. |
radius | number | — | Radius in meters. Required. |
strokeColor | string | theme primary | Outline color. |
fillColor | string | primary at 15% | Fill color. |
strokeWidth | number | 2 | Outline width. |
lineDashPattern | number[] | — | Dash pattern. |
zIndex | number | — | Stacking order. |
tappable | boolean | Boolean(onPress) | Receive taps. |
onPress | () => void | — | Tap handler. |
MapPolyline
| Prop | Type | Default | Description |
|---|---|---|---|
coordinates | MapLatLng[] | — | Path. Required. |
strokeColor | string | theme primary | Line color. |
strokeColors | string[] | — | Per-vertex gradient (same length as coordinates). |
strokeWidth | number | 3 | Line width. |
lineDashPattern | number[] | — | Dash pattern. |
lineCap | 'butt' | 'round' | 'square' | 'round' | Line cap. |
lineJoin | 'miter' | 'round' | 'bevel' | 'round' | Line join. |
geodesic | boolean | — | Follow the earth's curvature. |
zIndex | number | — | Stacking order. |
tappable | boolean | Boolean(onPress) | Receive taps. |
onPress | () => void | — | Tap handler. |
MapPolygon
| Prop | Type | Default | Description |
|---|---|---|---|
coordinates | MapLatLng[] | — | Outer ring. Required. |
holes | MapLatLng[][] | — | Inner rings. |
strokeColor | string | theme primary | Outline color. |
fillColor | string | primary at 15% | Fill color. |
strokeWidth | number | 2 | Outline width. |
lineDashPattern | number[] | — | Dash pattern. |
geodesic | boolean | — | Geodesic edges. |
zIndex | number | — | Stacking order. |
tappable | boolean | Boolean(onPress) | Receive taps. |
onPress | () => void | — | Tap handler. |
MapMarkerClusterGroup
Clusters its MapMarker children with a zero-dependency screen-space grid. The grid is recomputed every time the map settles.
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | — | MapMarker elements. Children without a coordinate render untouched. |
radius | number | 60 | Grid cell size in screen px. Markers in one cell merge. |
minPoints | number | 2 | Minimum number of members that form a cluster. |
maxZoom | number | 18 | At or above this zoom level, clustering is disabled. |
enabled | boolean | true | Turn clustering off. |
icon | (markerCount: number) => ReactNode | primary count bubble | Custom cluster view. |
onClusterPress | (cluster: MapCluster) => void | zoom to fit members | Press on a cluster. |
Controls
All controls accept position (an absolute-position className string, as on web) and className.
| Component | Props | Default position | Description |
|---|---|---|---|
MapControlContainer | children, position, className | 'top-2 left-2' | Generic overlay slot. |
MapZoomControl | position, className | 'top-2 left-2' | + / − buttons. |
MapLocateControl | position, className, watch?: boolean (false), zoom?: number (15), onLocationFound?(coordinate), onLocationError?(error) | 'bottom-2 right-2' | Centers on the device and shows a pulse marker. Press again to stop tracking. |
MapTypeControl (alias MapLayersControl) | position, className, mapTypes?: MapType[] (all four), labels?: Partial<Record<MapType, string>> | 'top-2 right-2' | Map-type action sheet. |
MapSearchControl | every PlaceAutocomplete prop, plus position and zoom?: number (14) | 'top-2 left-2 right-14' | Place search that recenters the map. |
MapSearchAreaControl | visible?: boolean (true), onPress (required), loading?: boolean (false), label?: string, position, className | 'top-3 left-0 right-0 items-center' | Floating "Search this area" pill. |
Hooks and helpers
| Export | Description |
|---|---|
useMap() | Context of the surrounding <Map> (MapContextValue | null): available, region, bounds, zoom, size, mapType, setMapType, locatedCoordinate, setLocatedCoordinate, userLocation, handle. |
markMapControl(Component) | Flags a custom component as a control so <Map> renders it in the overlay. |
regionFromCenter(center, zoom) | Region around a point for a zoom level. |
regionToBounds(region) / boundsToRegion(bounds) | Region ⇄ { north, south, east, west }. |
coordinatesToRegion(coordinates, margin?) | Region that contains every coordinate. |
isInBounds(coordinate, bounds) | Point-in-bounds test for viewport filtering. |
zoomToDelta(zoom) / deltaToZoom(longitudeDelta) | Web-mercator zoom ⇄ longitude delta (360 / 2^zoom). |
Translations
| Key | English fallback |
|---|---|
ui.map.zoomControls / ui.map.zoomIn / ui.map.zoomOut | Zoom controls / Zoom in / Zoom out |
ui.map.selectLayers | Select layers |
ui.map.mapTypeStandard / …Satellite / …Hybrid / …Terrain | Standard / Satellite / Hybrid / Terrain |
ui.map.trackLocation / ui.map.stopTracking / ui.map.locating | Track location / Stop tracking / Locating... |
ui.mapView.searchThisArea | Search this area |
ui.map.notAvailable / ui.map.installHint / ui.map.markerCount | Map not available / Install react-native-maps to display the map. / {count} markers |
Components
| Component | Description |
|---|---|
Map | Root: native map, overlay controls, context. |
MapMarker | Marker with a custom view, drag support and a callout. |
MapCallout / MapPopup | Marker popup. |
MapCircle / MapPolyline / MapPolygon | Shapes. |
MapMarkerClusterGroup | Marker clustering. |
MapControlContainer, MapZoomControl, MapLocateControl, MapTypeControl / MapLayersControl, MapSearchControl, MapSearchAreaControl | Overlay controls. |
Not ported from web: MapTileLayer / MapLayers / MapLayerGroup / MapFeatureGroup (the platform provider draws tiles, so use mapType), MapFullscreenControl (present a full-screen route instead), MapCircleMarker / MapRectangle (use MapCircle / MapPolygon), MapTooltip (use MapCallout), and the MapDraw* tools.
Type Exports
| Type | Description |
|---|---|
MapProps | Props of Map. |
MapHandle | Imperative handle. |
MapLatLng | { latitude, longitude }. |
MapRegion | MapLatLng plus latitudeDelta and longitudeDelta. |
MapBounds | { north, south, east, west }. |
MapCamera | { center, heading, pitch, zoom?, altitude? }. |
MapEdgePadding | { top, right, bottom, left }. |
MapFitOptions | { padding?, animated? }. |
MapRegionChangeDetails | { isGesture?, bounds, zoom }. |
MapType | 'standard' | 'satellite' | 'hybrid' | 'terrain'. |
MapProvider | 'default' | 'google'. |
MapMarkerItem | Legacy markers item (was named MapMarker). |
MapMarkerProps, MapCalloutProps, MapCircleProps, MapPolylineProps, MapPolygonProps | Layer props. |
MapMarkerClusterGroupProps, MapCluster | Clustering props and a cluster record (id, coordinate, count, coordinates, keys). |
MapControlContainerProps, MapZoomControlProps, MapLocateControlProps, MapTypeControlProps, MapSearchControlProps, MapSearchAreaControlProps | Control props. |
MapContextValue | Value returned by useMap(). |
LogActivityForm
Presentational multi-surface activity composer with Comment, Email, Call, Meeting, Task and Status tabs, each reporting its own typed submit callback.
MegaSelect
Searchable bottom-sheet select with categories, DocyrusIcon / image items, Tailwind accent colours, detail pages and a Choose CTA.