Docyrus

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.

iOSAndroid
Preview Map on your device

Scan with Expo Go

Download Expo Go, then scan the QR code to preview native components.

Installation

pnpm dlx @docyrus/cli add @docyrus/rn-map
Required Packages(2 packages)
pnpm 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 conceptMap API
markers[] with image / iconMapMarker with a custom child view (<Image> / DocyrusIcon) and tracksViewChanges={false} once loaded
basemap id (street, satellite, hybrid, terrain)mapType (standard, satellite, hybrid, terrain)
viewport boundsonRegionChangeComplete(region, { bounds, zoom })
"search this area" / isViewportStaleMapSearchAreaControl visible onPress
onMarkerMove / handleMarkerMoveMapMarker draggable onDragEnd(coordinate, id)
onMapClickonPress / onLongPress
center / bounds / fitfitToCoordinates prop or ref.fitToCoordinates()

API Reference

Map

PropTypeDefaultDescription
childrenReactNode—Map layers and controls.
refRef<MapHandle>—Imperative handle (React 19 ref-as-prop). See MapHandle.
regionMapRegion—Controlled region. Update it from onRegionChangeComplete.
initialRegionMapRegion—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.
fitToCoordinatesMapLatLng[]—Fits the viewport to these coordinates whenever the list changes (after the map is ready).
fitPaddingnumber | Partial<MapEdgePadding>48Edge padding used by fitToCoordinates (the prop and the handle).
singlePointZoomnumber14Zoom used when fitting a single coordinate.
mapType'standard' | 'satellite' | 'hybrid' | 'terrain'—Controlled map type. terrain needs the Google provider.
defaultMapTypeMapType'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.
showsUserLocationbooleanfalseShow the native user-location dot.
showsMyLocationButtonbooleanfalsePlatform my-location button (Android Google Maps).
showsCompassbooleantrueShow the compass.
showsScalebooleanfalseShow the scale bar.
showsBuildingsbooleantrueShow 3D buildings.
showsTrafficbooleanfalseShow traffic.
showsPointsOfInterestbooleantrueShow points of interest.
zoomEnabledbooleantrueAllow pinch zoom.
scrollEnabledbooleantrueAllow panning.
rotateEnabledbooleantrueAllow rotation.
pitchEnabledbooleantrueAllow pitch.
minZoomLevelnumber—Minimum zoom level.
maxZoomLevelnumber—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.
fallbackReactNode—Rendered instead of the default card when react-native-maps is missing.
classNamestring—Container classes. Give the map a height (h-[320px], flex-1).
latitudenumber0Legacy. Center latitude.
longitudenumber0Legacy. Center longitude.
zoomnumber10Legacy. Zoom level.
markersMapMarkerItem[]—Legacy. Plain markers with a default callout.
onMarkerPress(index: number, marker: MapMarkerItem) => void—Legacy. Press on a markers entry.

MapHandle

MethodSignatureDescription
animateToRegion(region: MapRegion, duration?: number) => voidAnimates to a region (350 ms by default).
animateCamera(camera: Partial<MapCamera>, duration?: number) => voidAnimates heading, pitch, center or zoom.
fitToCoordinates(coordinates: MapLatLng[], options?: MapFitOptions) => voidFits the viewport. A single coordinate centers on it at singlePointZoom.
zoomIn / zoomOut() => voidZooms one level. Region-based, so it works on Apple and Google maps.
setZoom(zoom: number) => voidAbsolute zoom that keeps the current center.
getRegion() => MapRegion | nullLast settled region.
getBounds() => MapBounds | nullLast settled bounds.
getCamera() => Promise<MapCamera | null>Native camera.

MapMarker

PropTypeDefaultDescription
coordinateMapLatLng—Marker position. Required.
idstring—Stable id. Passed to the press and drag callbacks.
titlestring—Default-callout title.
descriptionstring—Default-callout description.
childrenReactNode—Custom marker view and/or a MapCallout.
imageImageSourcePropType—Bitmap marker image (faster than a custom view).
pinColorstring—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.
tracksViewChangesbooleanplatform default (true)Re-render the custom view. Set false once it is static, for performance.
draggableboolean—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.
opacitynumber—Marker opacity.
flatboolean—Flat against the map (rotates with it).
rotationnumber—Rotation in degrees.
zIndexnumber—Stacking order.
stopPropagationboolean—Keep the press from reaching the map's onPress.

MapCallout (alias MapPopup)

PropTypeDefaultDescription
childrenReactNode—Callout content.
tooltipbooleantruetrue renders a themed popover bubble. false uses the native callout bubble.
onPress() => void—Press on the callout.
classNamestring—Classes for the content wrapper.

MapCircle

PropTypeDefaultDescription
centerMapLatLng—Center. Required.
radiusnumber—Radius in meters. Required.
strokeColorstringtheme primaryOutline color.
fillColorstringprimary at 15%Fill color.
strokeWidthnumber2Outline width.
lineDashPatternnumber[]—Dash pattern.
zIndexnumber—Stacking order.
tappablebooleanBoolean(onPress)Receive taps.
onPress() => void—Tap handler.

MapPolyline

PropTypeDefaultDescription
coordinatesMapLatLng[]—Path. Required.
strokeColorstringtheme primaryLine color.
strokeColorsstring[]—Per-vertex gradient (same length as coordinates).
strokeWidthnumber3Line width.
lineDashPatternnumber[]—Dash pattern.
lineCap'butt' | 'round' | 'square''round'Line cap.
lineJoin'miter' | 'round' | 'bevel''round'Line join.
geodesicboolean—Follow the earth's curvature.
zIndexnumber—Stacking order.
tappablebooleanBoolean(onPress)Receive taps.
onPress() => void—Tap handler.

MapPolygon

PropTypeDefaultDescription
coordinatesMapLatLng[]—Outer ring. Required.
holesMapLatLng[][]—Inner rings.
strokeColorstringtheme primaryOutline color.
fillColorstringprimary at 15%Fill color.
strokeWidthnumber2Outline width.
lineDashPatternnumber[]—Dash pattern.
geodesicboolean—Geodesic edges.
zIndexnumber—Stacking order.
tappablebooleanBoolean(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.

PropTypeDefaultDescription
childrenReactNode—MapMarker elements. Children without a coordinate render untouched.
radiusnumber60Grid cell size in screen px. Markers in one cell merge.
minPointsnumber2Minimum number of members that form a cluster.
maxZoomnumber18At or above this zoom level, clustering is disabled.
enabledbooleantrueTurn clustering off.
icon(markerCount: number) => ReactNodeprimary count bubbleCustom cluster view.
onClusterPress(cluster: MapCluster) => voidzoom to fit membersPress on a cluster.

Controls

All controls accept position (an absolute-position className string, as on web) and className.

ComponentPropsDefault positionDescription
MapControlContainerchildren, position, className'top-2 left-2'Generic overlay slot.
MapZoomControlposition, className'top-2 left-2'+ / − buttons.
MapLocateControlposition, 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.
MapSearchControlevery PlaceAutocomplete prop, plus position and zoom?: number (14)'top-2 left-2 right-14'Place search that recenters the map.
MapSearchAreaControlvisible?: 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

ExportDescription
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

KeyEnglish fallback
ui.map.zoomControls / ui.map.zoomIn / ui.map.zoomOutZoom controls / Zoom in / Zoom out
ui.map.selectLayersSelect layers
ui.map.mapTypeStandard / …Satellite / …Hybrid / …TerrainStandard / Satellite / Hybrid / Terrain
ui.map.trackLocation / ui.map.stopTracking / ui.map.locatingTrack location / Stop tracking / Locating...
ui.mapView.searchThisAreaSearch this area
ui.map.notAvailable / ui.map.installHint / ui.map.markerCountMap not available / Install react-native-maps to display the map. / {count} markers

Components

ComponentDescription
MapRoot: native map, overlay controls, context.
MapMarkerMarker with a custom view, drag support and a callout.
MapCallout / MapPopupMarker popup.
MapCircle / MapPolyline / MapPolygonShapes.
MapMarkerClusterGroupMarker clustering.
MapControlContainer, MapZoomControl, MapLocateControl, MapTypeControl / MapLayersControl, MapSearchControl, MapSearchAreaControlOverlay 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

TypeDescription
MapPropsProps of Map.
MapHandleImperative handle.
MapLatLng{ latitude, longitude }.
MapRegionMapLatLng 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'.
MapMarkerItemLegacy markers item (was named MapMarker).
MapMarkerProps, MapCalloutProps, MapCircleProps, MapPolylineProps, MapPolygonPropsLayer props.
MapMarkerClusterGroupProps, MapClusterClustering props and a cluster record (id, coordinate, count, coordinates, keys).
MapControlContainerProps, MapZoomControlProps, MapLocateControlProps, MapTypeControlProps, MapSearchControlProps, MapSearchAreaControlPropsControl props.
MapContextValueValue returned by useMap().

On this page