react-native-nitro-map
v0.0.11
Published
A high-performance React Native map library powered by Nitro Modules with support for Apple Maps, Google Maps, and Yandex Maps. Features native clustering, custom markers, and seamless cross-platform integration.
Readme
react-native-nitro-map
⚠️ BETA - Work in Progress
This library is currently in active development and beta testing. APIs may change without notice. Do not use in production until a stable release is announced.
A high-performance multi-provider maps library for React Native, built with Nitro Modules for native speed.
✨ Features
- 🚀 Native Performance - Built with Nitro Modules for maximum speed
- 🗺️ Multi-Provider - Apple Maps, Google Maps, and Yandex Maps support
- 📍 High-Performance Clustering - C++ clustering engine for 30,000+ markers
- 🎨 Customizable Markers - Price and Image marker styles
- 🌙 Dark Mode Support - Built-in dark theme
- 📱 iOS & Android - Full platform support
Supported Providers
| Provider | iOS | Android | Notes | | --------------- | ---------- | ------- | ------------------- | | Apple Maps | ✅ iOS 17+ | ❌ | No API key required | | Google Maps | ✅ | ✅ | Requires API key | | Yandex Maps | ✅ | ✅ | Requires API key |
Installation
npm install react-native-nitro-map react-native-nitro-modules
# or
yarn add react-native-nitro-map react-native-nitro-modulesiOS Setup
cd ios && pod installAndroid Setup
We recommend using react-native-config to securely manage API keys via .env files.
1. Install react-native-config
npm install react-native-config
# or
yarn add react-native-config2. Create .env file (add to .gitignore)
GOOGLE_MAP=your_google_maps_api_key
YANDEX_MAP=your_yandex_maps_api_key3. Update android/app/build.gradle
apply plugin: "com.android.application"
apply plugin: "com.facebook.react"
// Add this line to load .env variables
apply from: project(':react-native-config').projectDir.getPath() + "/dotenv.gradle"
android {
defaultConfig {
// Expose .env variables to AndroidManifest
manifestPlaceholders = [
GOOGLE_MAP: project.env.get("GOOGLE_MAP") ?: "",
YANDEX_MAP: project.env.get("YANDEX_MAP") ?: ""
]
}
}4. Update android/app/src/main/AndroidManifest.xml
<application>
<!-- Google Maps -->
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="${GOOGLE_MAP}"/>
<!-- Yandex Maps (if using) -->
<meta-data
android:name="com.yandex.android.mapkit.ApiKey"
android:value="${YANDEX_MAP}"/>
</application>Alternative: Hardcoded Keys (Not Recommended)
If you prefer not to use react-native-config, you can hardcode keys directly in AndroidManifest.xml:
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="YOUR_GOOGLE_MAPS_API_KEY"/>⚠️ Warning: Hardcoding API keys in source files is insecure. Keys can be extracted from the APK and may be exposed in version control.
Initialization
Before using the map, you must initialize it with your API key:
import { useEffect } from 'react';
import {
NitroMapInitialize,
IsNitroMapInitialized,
} from 'react-native-nitro-map';
import Config from 'react-native-config';
function App() {
useEffect(() => {
// Initialize once at app startup
// Google Maps
NitroMapInitialize(Config.GOOGLE_MAP!, 'google');
// OR Yandex Maps
// NitroMapInitialize(Config.YANDEX_MAP!, 'yandex');
// OR Apple Maps (no API key required)
// NitroMapInitialize('', 'apple');
// Check if initialized
if (IsNitroMapInitialized()) {
console.log('Map is ready!');
}
}, []);
return <NitroMap provider="google" />;
}Important: You must call
NitroMapInitializebefore rendering anyNitroMapcomponent.
Platform Notes
| Platform | Google Maps | Yandex Maps |
| -------- | ---------------------------------------------- | ---------------------------- |
| iOS | API key passed via NitroMapInitialize | API key via NitroMapInitialize |
| Android | API key from AndroidManifest.xml (build time)| API key via NitroMapInitialize |
On Android, Google Maps reads the API key from the manifest at build time. The NitroMapInitialize call still initializes the SDK, but the key must be in the manifest (use react-native-config to inject it securely from .env).
Quick Start
import { useEffect } from 'react';
import {
NitroMap,
NitroMapInitialize,
PriceMarker,
ImageMarker,
} from 'react-native-nitro-map';
import Config from 'react-native-config';
export default function App() {
// Initialize once at app startup
useEffect(() => {
NitroMapInitialize(Config.GOOGLE_MAP!, 'google');
}, []);
return (
<NitroMap
provider="google" // "google" | "apple" | "yandex"
initialRegion={{
latitude: 41.2995,
longitude: 69.2401,
latitudeDelta: 0.05,
longitudeDelta: 0.05,
}}
>
<PriceMarker
coordinate={{ latitude: 41.2995, longitude: 69.2401 }}
price="150K"
currency="USD"
/>
<ImageMarker
coordinate={{ latitude: 41.305, longitude: 69.245 }}
imageUrl="https://example.com/avatar.jpg"
width={50}
height={50}
/>
</NitroMap>
);
}Provider Selection
Choose your map provider based on your needs:
// Apple Maps - No API key, iOS 17+ only
<NitroMap provider="apple" />
// Google Maps - Requires API key, best coverage
<NitroMap provider="google" />
// Yandex Maps - Requires API key, best for CIS regions
<NitroMap provider="yandex" />| Feature | Apple | Google | Yandex | | -------------------- | ------ | ------ | ------------------- | | API Key Required | ❌ | ✅ | ✅ | | Satellite View | ✅ | ✅ | ⚠️ Needs permission | | Clustering Engine | C++ | C++ | C++ | | Min iOS Version | 17.0 | 12.0 | 12.0 | | Max Markers (tested) | 30,000 | 30,000 | 30,000 |
NitroMap Props
| Prop | Type | Default | Description |
| ----------------------- | --------------------------------------- | ------------ | -------------------------- |
| provider | 'google' \| 'apple' \| 'yandex' | 'google' | Map provider |
| initialRegion | Region | - | Initial map region |
| showsUserLocation | boolean | false | Show user location dot |
| showsMyLocationButton | boolean | false | Show location button |
| mapType | 'standard' \| 'satellite' \| 'hybrid' | 'standard' | Map style |
| darkMode | boolean | false | Enable dark theme |
| zoomEnabled | boolean | true | Allow zoom gestures |
| scrollEnabled | boolean | true | Allow scroll gestures |
| rotateEnabled | boolean | true | Allow rotation gestures |
| pitchEnabled | boolean | true | Allow pitch/tilt gestures |
| clusterConfig | ClusterConfig | - | Marker clustering settings |
Events
| Prop | Type | Description |
| ------------------------ | ------------------------------------ | -------------------- |
| onMapReady | () => void | Map finished loading |
| onPress | (event: MapPressEvent) => void | Map tap |
| onLongPress | (event: MapPressEvent) => void | Map long press |
| onRegionChange | (event: RegionChangeEvent) => void | Region changing |
| onRegionChangeComplete | (event: RegionChangeEvent) => void | Region change ended |
| onMarkerPress | (event: MarkerPressEvent) => void | Any marker tapped |
| onClusterPress | (event: ClusterPressEvent) => void | Cluster tapped |
NitroMap Methods
Access methods via ref:
const mapRef = useRef<NitroMapRef>(null);
// Animate to region
mapRef.current?.animateToRegion(
{
latitude: 41.2995,
longitude: 69.2401,
latitudeDelta: 0.01,
longitudeDelta: 0.01,
},
500
);
// Select marker (changes color, no React re-render!)
mapRef.current?.selectMarker('marker-id');
// Fit to coordinates
mapRef.current?.fitToCoordinates(
[
{ latitude: 41.299, longitude: 69.24 },
{ latitude: 41.31, longitude: 69.25 },
],
{ top: 50, bottom: 50, left: 50, right: 50 },
true
);
// Get camera
const camera = await mapRef.current?.getCamera();
// Get boundaries
const bounds = await mapRef.current?.getMapBoundaries();| Method | Description |
| ----------------------------------------------- | ------------------------------ |
| animateToRegion(region, duration?) | Animate camera to region |
| fitToCoordinates(coords, padding?, animated?) | Fit map to coordinates |
| animateCamera(camera, duration?) | Animate to camera position |
| setCamera(camera) | Set camera immediately |
| getCamera() | Get current camera |
| getMapBoundaries() | Get visible region bounds |
| addMarker(marker) | Add marker programmatically |
| addMarkers(markers) | Add multiple markers |
| updateMarker(marker) | Update existing marker |
| removeMarker(id) | Remove marker by ID |
| clearMarkers() | Remove all markers |
| selectMarker(id) | Select marker (native styling) |
| setClusteringEnabled(enabled) | Toggle clustering |
| refreshClusters() | Force clustering refresh |
PriceMarker Component
Display price tags on the map with zero re-render selection:
import { PriceMarker, Colors } from 'react-native-nitro-map';
<PriceMarker
coordinate={{ latitude: 41.2995, longitude: 69.2401 }}
price="150K"
currency="USD"
selected={false}
backgroundColor={Colors.white}
selectedBackgroundColor={Colors.red}
textColor={Colors.black}
selectedTextColor={Colors.white}
fontSize={14}
onPress={() => console.log('Pressed!')}
/>;PriceMarker Props
| Prop | Type | Default | Description |
| ------------------------- | ------------- | ------------------ | ------------------------- |
| coordinate | Coordinate | Required | Marker position |
| price | string | Required | Price text (e.g., "150K") |
| id | string | auto-generated | Unique identifier |
| currency | string | - | Currency code |
| selected | boolean | false | Selected state |
| backgroundColor | MarkerColor | white | Background color |
| selectedBackgroundColor | MarkerColor | - | Background when selected |
| textColor | MarkerColor | black | Text color |
| selectedTextColor | MarkerColor | - | Text color when selected |
| fontSize | number | 14 | Font size in pixels |
| paddingHorizontal | number | - | Horizontal padding |
| paddingVertical | number | - | Vertical padding |
| shadowOpacity | number | - | Shadow opacity (0-1) |
| draggable | boolean | false | Allow dragging |
| opacity | number | 1 | Marker opacity (0-1) |
| anchor | Point | {x: 0.5, y: 0.5} | Anchor point |
| clusteringEnabled | boolean | true | Include in clustering |
| animation | string | 'none' | Appear animation |
ImageMarker Component
Display images as map markers:
import { ImageMarker, Colors } from 'react-native-nitro-map';
<ImageMarker
coordinate={{ latitude: 41.2995, longitude: 69.2401 }}
imageUrl="https://example.com/avatar.jpg"
width={50}
height={50}
cornerRadius={25}
borderWidth={2}
borderColor={Colors.white}
/>;ImageMarker Props
| Prop | Type | Default | Description |
| ------------------- | ------------- | ------------------ | ---------------------- |
| coordinate | Coordinate | Required | Marker position |
| id | string | auto-generated | Unique identifier |
| imageUrl | string | - | URL of the image |
| imageBase64 | string | - | Base64-encoded image |
| width | number | 50 | Image width in pixels |
| height | number | 50 | Image height in pixels |
| cornerRadius | number | 8 | Corner radius |
| borderWidth | number | 0 | Border thickness |
| borderColor | MarkerColor | gray | Border color |
| draggable | boolean | false | Allow dragging |
| opacity | number | 1 | Marker opacity (0-1) |
| anchor | Point | {x: 0.5, y: 0.5} | Anchor point |
| clusteringEnabled | boolean | true | Include in clustering |
| animation | string | 'none' | Appear animation |
Marker Events
| Prop | Type | Description |
| ------------- | ----------------- | ------------- |
| onPress | () => void | Marker tapped |
| onDragStart | (coord) => void | Drag started |
| onDrag | (coord) => void | Dragging |
| onDragEnd | (coord) => void | Drag ended |
Selection Example
const MARKERS = [
{ id: 'marker-1', latitude: 41.29, longitude: 69.24, price: '100K' },
{ id: 'marker-2', latitude: 41.3, longitude: 69.25, price: '150K' },
];
function App() {
const mapRef = useRef<NitroMapRef>(null);
const onMarkerPress = useCallback((event: MarkerPressEvent) => {
// Native handles selection styling - ZERO React re-renders!
mapRef.current?.selectMarker(event.id);
// Optionally trigger other UI (bottom sheet, etc.)
fetchMarkerDetails(event.id);
}, []);
return (
<NitroMap ref={mapRef} onMarkerPress={onMarkerPress}>
{MARKERS.map((m) => (
<PriceMarker
key={m.id}
id={m.id}
coordinate={{ latitude: m.latitude, longitude: m.longitude }}
price={m.price}
currency="USD"
backgroundColor={Colors.white}
selectedBackgroundColor={Colors.red}
textColor={Colors.black}
selectedTextColor={Colors.white}
/>
))}
</NitroMap>
);
}C++ Clustering Engine
All three providers use the same high-performance C++ clustering engine:
<NitroMap
clusterConfig={{
enabled: true,
minimumClusterSize: 2,
maxZoom: 20, // Clusters break into markers above this zoom
backgroundColor: { r: 255, g: 87, b: 51, a: 255 },
textColor: { r: 255, g: 255, b: 255, a: 255 },
borderWidth: 2,
borderColor: { r: 255, g: 255, b: 255, a: 255 },
animatesClusters: true,
animationDuration: 0.3,
animationStyle: 'default',
}}
onClusterPress={(event) => {
console.log(`Cluster with ${event.count} markers`);
console.log('Marker IDs:', event.markerIds);
// Zoom into cluster
mapRef.current?.animateToRegion({
...event.coordinate,
latitudeDelta: 0.01,
longitudeDelta: 0.01,
});
}}
>
{markers.map((m) => (
<PriceMarker key={m.id} {...m} clusteringEnabled={true} />
))}
</NitroMap>Performance
| Markers | Apple | Google | Yandex | | ------- | ---------- | ---------- | ---------- | | 100 | ⚡ Instant | ⚡ Instant | ⚡ Instant | | 1,000 | ⚡ Fast | ⚡ Fast | ⚡ Fast | | 10,000 | ✅ Smooth | ✅ Smooth | ✅ Smooth | | 30,000 | ✅ Works | ✅ Works | ✅ Works |
Color Helpers
import { rgb, hex, Colors } from 'react-native-nitro-map';
// RGB helper
const red = rgb(255, 0, 0); // { r: 255, g: 0, b: 0, a: 255 }
const semiRed = rgb(255, 0, 0, 128); // 50% opacity
// Hex helper
const blue = hex('#0000FF');
const semiBlue = hex('#0000FF', 128);
// Preset colors
Colors.red; // { r: 255, g: 59, b: 48, a: 255 }
Colors.blue; // { r: 0, g: 122, b: 255, a: 255 }
Colors.green; // { r: 76, g: 175, b: 80, a: 255 }
Colors.white; // { r: 255, g: 255, b: 255, a: 255 }
Colors.black; // { r: 0, g: 0, b: 0, a: 255 }Types
type Region = {
latitude: number;
longitude: number;
latitudeDelta: number;
longitudeDelta: number;
};
type Coordinate = {
latitude: number;
longitude: number;
};
type Camera = {
center: Coordinate;
pitch: number;
heading: number;
altitude: number;
zoom: number;
};
type MarkerColor = {
r: number; // 0-255
g: number; // 0-255
b: number; // 0-255
a: number; // 0-255
};
type MapProvider = 'google' | 'apple' | 'yandex';
type MapType = 'standard' | 'satellite' | 'hybrid';
type MarkerStyle = 'default' | 'image' | 'priceMarker';
type MarkerAnimation = 'none' | 'pop' | 'fadeIn';Troubleshooting
Apple Maps shows "+1 more" labels
This is MapKit's native clustering. Make sure you're using provider="apple" with the C++ clustering engine enabled.
Yandex satellite mode shows "Forbidden"
Yandex satellite tiles require explicit permission in the Yandex Developer Console.
Markers not appearing
- Check that coordinates are within visible region
- Verify marker
zIndexif markers overlap - Ensure
clusteringEnabledis set correctly
Performance issues with many markers
- Enable clustering:
clusterConfig={{ enabled: true }} - Use
addMarkers()for batch adding instead of individualaddMarker()calls - Consider reducing
maxZoomto keep more markers clustered
License
MIT
Made with Nitro Modules
