Skip to content

Latest commit

History

130 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

MapConductor React SDK

React Native iOS frameworks

The iOS native SDK is consumed as XCFrameworks. Build and synchronize the default Core, extension, Google Maps, and MapLibre artifacts from the sibling ios-sdk repo:

npm run ios:frameworks

Use npm run ios:frameworks:sync when the XCFrameworks have already been built.

MapConductor provides a shared TypeScript and React API for Google Maps and MapLibre. The same core geometry, state, overlay, and controller abstractions are used by the web and React Native packages.

Features

  • Google Maps and MapLibre providers
  • Web and React Native entry points
  • Observable state objects for maps and overlays
  • Marker, circle, polyline, polygon, GroundImage, and RasterLayer components
  • Efficient bulk marker transport with <Markers states={states} />
  • Heatmap and marker-clustering extensions
  • Provider-independent camera and event APIs

Packages

PackagePurpose
@mapconductor/js-sdk-coreGeometry, observable state, controllers, overlays, and shared types
@mapconductor/js-sdk-reactShared web and React Native components
@mapconductor/react-for-googlemapsGoogle Maps provider and map view
@mapconductor/react-for-maplibreMapLibre provider and map view
@mapconductor/react-heatmapHeatmap extension
@mapconductor/react-marker-clusteringMarker-clustering extension
@mapconductor/react-iconsShared icon components

The former js-sdk-reactnative, reactnative-for-googlemaps, and reactnative-for-maplibre packages have been merged into js-sdk-react and the corresponding react-for-* packages. New code should not depend on the old packages.

Installation

Install the core and React packages together with one or both providers:

npm install \
@mapconductor/js-sdk-core \
@mapconductor/js-sdk-react \
@mapconductor/react-for-googlemaps \
@mapconductor/react-for-maplibre

For React Native, normal package autolinking discovers the Android provider modules. Google Maps API keys must be supplied through local.properties, Gradle properties, or GOOGLE_MAPS_API_KEY; do not put keys in source files.

Module formats and SSR

These packages target a bundler. Vite, webpack, Next.js and Metro all resolve them correctly, and every provider is verified rendering in a real browser before release. Loading a provider through Node's own resolver — plain node, an SSR entry that is not processed by a bundler, or Vitest with the node environment — is a different matter, because the underlying map SDKs are browser libraries.

Measured on 0.2.0 by importing each published package in an empty project. The same results hold for 0.1.3, so none of this is new:

Packageimport in Noderequire() in NodeWhy
react-for-arcgisfailsfails@arcgis/core/views/MapView imports .css, which Node cannot load. Unavoidable: any map needs MapView
react-for-leafletfailsfailsleaflet touches window at module scope
react-for-azuremapsfailsfailsazure-maps-control touches window at module scope
react-for-tomtomfailsfailsreads maplibre-gl/package.json, which Node requires an import attribute for
react-for-mapplsfailsworksmappls-web-maps is CJS; its named exports are not statically analysable
react-for-maplibreworksfailsmaplibre-gl v6 is ESM-only and declares no CJS entry
react-for-maptilerworksfailssame as react-for-maplibre
everything elseworksworks

Two consequences worth knowing:

  • For SSR, import providers lazily on the client.examples/basic does this with lazy(() => import('./providers/...')), which is why its Vite SSR build passes. A static top-level import of a browser-only provider will break any server render that is not bundler-processed.
  • react-for-maplibre, react-for-maptiler and react-for-tomtom advertise a CJS entry that cannot actually work, because maplibre-gl v6 dropped CJS. Use ESM with these three. The require condition should be removed from their exports in a future release.

react-for-openlayers used to fail Node ESM as well, because of a directory import (ol/proj) in its own source rather than anything upstream. Fixed in 0.2.1.

State-first API

MapConductor state objects are mutable and observable. Create a state once, retain it for the component lifetime, and update its properties directly. Property updates are sent to the registered provider renderer without replacing the state or rebuilding the React overlay tree.

import{useState}from'react';import{createCircleState,createGeoPoint,}from'@mapconductor/js-sdk-core';import{Circle}from'@mapconductor/js-sdk-react';functionRadiusCircle(){const[circleState]=useState(()=>createCircleState({id: 'radius-circle',center: createGeoPoint({latitude: 35.6812,longitude: 139.7671}),radiusMeters: 1000,strokeColor: '#2563eb',fillColor: 'rgba(37, 99, 235, 0.3)',}));constsetRadius=(radiusMeters: number)=>{circleState.radiusMeters=radiusMeters;};return<Circlestate={circleState}/>;}

Components also provide convenience props such as <Marker position={point} />. For frequently updated overlays, prefer a retained state and <Marker state={markerState} />; this avoids extra React effects and recomposition.

For large marker collections, use <Markers states={states} /> instead of creating one React component per marker.

React Native quick start

The explicit @mapconductor/js-sdk-react/native entry point is recommended in shared workspaces and tooling that does not automatically resolve the react-native export condition.

Google Maps

import{useState}from'react';import{StyleSheet}from'react-native';import{createGeoPoint,createMapCameraPosition,createMarkerState,}from'@mapconductor/js-sdk-core';import{Marker}from'@mapconductor/js-sdk-react/native';import{GoogleMapDesign,GoogleMapView,useGoogleMapViewState,}from'@mapconductor/react-for-googlemaps';constTOKYO=createGeoPoint({latitude: 35.6812,longitude: 139.7671});exportfunctionGoogleMapExample(){constmapState=useGoogleMapViewState({id: 'google-map',mapDesignType: GoogleMapDesign.Normal,cameraPosition: createMapCameraPosition({position: TOKYO,zoom: 12}),});const[markerState]=useState(()=>createMarkerState({id: 'tokyo',position: TOKYO}));return(<GoogleMapViewstate={mapState}style={styles.map}><Markerstate={markerState}/></GoogleMapView>);}conststyles=StyleSheet.create({map: {flex: 1}});

MapLibre

import{useState}from'react';import{StyleSheet}from'react-native';import{createGeoPoint,createMapCameraPosition,createMarkerState,}from'@mapconductor/js-sdk-core';import{Marker}from'@mapconductor/js-sdk-react/native';import{MapLibreDesign,MapLibreView,useMapLibreViewState,}from'@mapconductor/react-for-maplibre';constTOKYO=createGeoPoint({latitude: 35.6812,longitude: 139.7671});exportfunctionMapLibreExample(){constmapState=useMapLibreViewState({id: 'maplibre-map',mapDesignType: MapLibreDesign.DemoTiles,cameraPosition: createMapCameraPosition({position: TOKYO,zoom: 12}),});const[markerState]=useState(()=>createMarkerState({id: 'tokyo',position: TOKYO}));return(<MapLibreViewstate={mapState}style={styles.map}><Markerstate={markerState}/></MapLibreView>);}conststyles=StyleSheet.create({map: {flex: 1}});

The React Native example uses a common MapViewContainer to select the provider from the runtime type of the retained view state. See examples/reactnative-basic/src/pages/MapViewContainer.tsx.

Overlays may be declared before onMapLoaded. The provider controller queues pending compositions until the native map is ready, so page-level readiness gates should not be necessary for ordinary overlays.

Web quick start

The same state and overlay components are available from the default React entry point:

import{useState}from'react';import{createGeoPoint,createMapCameraPosition,createMarkerState,}from'@mapconductor/js-sdk-core';import{Marker}from'@mapconductor/js-sdk-react';import{GoogleMapDesign,GoogleMapView,useGoogleMapViewState,}from'@mapconductor/react-for-googlemaps';constTOKYO=createGeoPoint({latitude: 35.6812,longitude: 139.7671});exportfunctionWebGoogleMap({ apiKey }: {apiKey: string}){constmapState=useGoogleMapViewState({id: 'web-google-map',mapDesignType: GoogleMapDesign.Normal,cameraPosition: createMapCameraPosition({position: TOKYO,zoom: 12}),});const[markerState]=useState(()=>createMarkerState({id: 'tokyo',position: TOKYO}));return(<GoogleMapViewstate={mapState}apiKey={apiKey}style={{height: 480}}><Markerstate={markerState}/></GoogleMapView>);}

For MapLibre web maps, import the provider stylesheet once:

import'@mapconductor/react-for-maplibre/style.css';

Overlays

All ordinary overlays support both a retained-state form and convenience props. The retained-state form is preferred for updates.

Marker

const[markerState]=useState(()=>createMarkerState({id: 'draggable-marker',
position,draggable: true,onDrag: (state)=>console.log(state.position),}));markerState.position=nextPosition;return<Markerstate={markerState}/>;

Polyline and Polygon

const[polylineState]=useState(()=>createPolylineState({id: 'route',
points,strokeColor: '#dc2626',strokeWidth: 6,}));const[polygonState]=useState(()=>createPolygonState({id: 'area',points: outerRing,holes: [innerRing],strokeColor: '#1d4ed8',fillColor: 'rgba(37, 99, 235, 0.4)',}));polylineState.strokeWidth=10;polygonState.fillColor='rgba(37, 99, 235, 0.7)';return(<><Polylinestate={polylineState}/><Polygonstate={polygonState}/></>);

GroundImage

GroundImage supports bounds, image, opacity, and click updates. React Native Android accepts android.resource:, content:, file:, and data:image image URIs.

const[groundImageState]=useState(()=>createGroundImageState({id: 'ground-image',bounds: createGeoRectBounds({ southWest, northEast }),imageUrl: 'android.resource://com.example.app/drawable/ground_image',opacity: 0.5,onClick: ({ clicked })=>console.log(clicked),}));groundImageState.opacity=0.8;groundImageState.bounds=nextBounds;return<GroundImagestate={groundImageState}/>;

RasterLayer

Raster sources support URL templates, TileJSON, and ArcGIS services.

constsource=RasterLayerSource.UrlTemplate({template: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',tileSize: 256,});const[rasterLayerState]=useState(()=>createRasterLayerState({id: 'osm',
source,opacity: 1,}));rasterLayerState.opacity=0.5;return<RasterLayerstate={rasterLayerState}/>;

Raster layers created by JSX and raster layers owned by native extensions share the provider collector. Clearing one composition preserves unrelated extension layers.

React Native Android architecture

Ordinary markers use the native provider controller directly:

JS MarkerState
-> React Native batch bridge
-> provider wrapper
-> Android SDK provider controller
-> Android SDK core
-> Google Maps or MapLibre SDK

Large marker compositions build one icon registry per generation, send structure-of-arrays batches of 500 markers, wait for a native ACK after each batch, and commit the generation after the final ACK. Icon payloads are not repeated in every batch.

Heatmap and marker clustering use the generic native extension boundary. Extension-specific commands are not added to the public provider controller interfaces.

Examples

The React Native example includes provider-comparable pages for maps, markers, marker animation, post offices, clustering, circles, polylines, polygons, GroundImage, RasterLayer, and heatmap.

Run the web example:

npm run dev --workspace @mapconductor/example-basic

Run the React Native Android example:

npm run dev:rn:android

Repository structure

react-sdk/
├── js-sdk-core/ # Provider-independent state and controller APIs
├── js-sdk-react/ # Shared React and React Native bindings
├── react-for-googlemaps/ # Google Maps web/RN provider
├── react-for-maplibre/ # MapLibre web/RN provider
├── react-icons/ # Shared icons
├── react-heatmap/ # Heatmap extension
├── react-marker-clustering/ # Marker-clustering extension
├── examples/basic/ # Web example
├── examples/reactnative-basic/ # React Native example
└── tests/ # Integration and end-to-end tests

Development

See llm.md for concise implementation guidance covering the state model, React Native bridge, GroundImage, RasterLayer, and example conventions. Repository contribution rules are in AGENTS.md.

Install dependencies and build all workspace packages:

npm install
npm run build

Common commands:

npm run lint
npm run test
npm run dev
npm run dev:packages
npm run dev:examples

For focused React Native verification:

npm run build --workspace @mapconductor/js-sdk-react
npm run build --workspace @mapconductor/react-for-googlemaps
npm run build --workspace @mapconductor/react-for-maplibre
npx tsc --noEmit --ignoreDeprecations 5.0 -p examples/reactnative-basic/tsconfig.json
JAVA_HOME="/Applications/Android Studio.app/Contents/jbr/Contents/Home" \
./gradlew \
:android:mapconductor_js-sdk-react:compileDebugKotlin \
:android:mapconductor_react-for-googlemaps:compileDebugKotlin \
:android:mapconductor_react-for-maplibre:compileDebugKotlin \
:android:app:compileDebugKotlin

The provider and shared SDK directories can be nested Git worktrees or submodules. Check them independently with git -C <package> diff --check.

Android SDK dependencies

The React Native Android packages consume the sibling MapConductor Android SDK through MavenLocal:

  • com.mapconductor:for-googlemaps:1.2.0
  • com.mapconductor:for-maplibre:1.2.0
  • com.mapconductor:heatmap:1.0.2
  • com.mapconductor:marker-clustering:1.0.2

When changing the Android SDK, compile and publish the affected module to MavenLocal before rebuilding this repository.

License

Apache License 2.0. See LICENSE.

Related projects

Releases

Packages

Contributors

Languages