React Map Integration
This guide shows you how to integrate MapMetrics into a React application.
Prerequisites
- Node.js and npm installed
- Basic knowledge of React
- MapMetrics API key and style URL
Quick Start
1. Create React App
npx create-react-app my-map-app
cd my-map-app2. Install MapMetrics GL Package
npm install @mapmetrics/mapmetrics-glPackage Details:
- NPM Package: @mapmetrics/mapmetrics-gl
- Current Version: ^0.5.7
3. Create Map Component
Create a new file src/MapComponent.jsx:
import React, { useEffect, useRef } from 'react';
import mapmetricsgl from '@mapmetrics/mapmetrics-gl';
import '@mapmetrics/mapmetrics-gl/dist/mapmetrics-gl.css';
const MapComponent = () => {
const mapContainer = useRef(null);
const map = useRef(null);
useEffect(() => {
if (map.current) return; // Initialize map only once
// Replace with your complete style URL from MapMetrics Portal
const YOUR_STYLE_URL = 'https://gateway.mapmetrics-atlas.net/styles/?fileName=YOUR_USER_ID/YOUR_STYLE.json&token=YOUR_TOKEN';
map.current = new mapmetricsgl.Map({
container: mapContainer.current,
style: YOUR_STYLE_URL,
center: [-74.006, 40.7128], // New York City
zoom: 12
});
// Add navigation controls
map.current.addControl(new mapmetricsgl.NavigationControl(), 'top-right');
// Cleanup on unmount
return () => {
if (map.current) {
map.current.remove();
map.current = null;
}
};
}, []);
return (
<div
ref={mapContainer}
style={{
width: '100%',
height: '500px'
}}
/>
);
};
export default MapComponent;4. Use Map Component in App
Update src/App.js:
import React from 'react';
import './App.css';
import MapComponent from './MapComponent';
function App() {
return (
<div className="App">
<h1>My MapMetrics App</h1>
<MapComponent />
</div>
);
}
export default App;5. Add CSS (Optional)
Update src/App.css:
.App {
text-align: center;
padding: 20px;
}
h1 {
margin-bottom: 20px;
}6. Run Your App
npm startYour map should now appear at http://localhost:3000
Important Notes
Style URL
Replace YOUR_STYLE_URL with the complete URL you get from MapMetrics Portal. It should look like:
const YOUR_STYLE_URL = 'https://gateway.mapmetrics-atlas.net/styles/?fileName=753b9b14-2fcc-44d3-b273-c8b2b701647a/Bicolor.json&token=eyJhbGc...';Package Information
MapMetrics GL is available on NPM:
- ✅ Install:
npm install @mapmetrics/mapmetrics-gl - ✅ Import:
import mapmetricsgl from '@mapmetrics/mapmetrics-gl' - ✅ CSS:
import '@mapmetrics/mapmetrics-gl/dist/mapmetrics-gl.css' - 📦 NPM: @mapmetrics/mapmetrics-gl
Map Container Height
The map container must have a height. Set it via inline style or CSS:
// Option 1: Inline style
<div ref={mapContainer} style={{ height: '500px', width: '100%' }} />
// Option 2: CSS class
<div ref={mapContainer} className="map-container" />.map-container {
height: 500px;
width: 100%;
}Error Handling (Recommended)
It's highly recommended to add error handling to detect if users forget to add their token. This provides a much better user experience than a blank screen:
// MapComponent.jsx - With Robust Error Handling
import React, { useEffect, useRef, useState } from 'react';
import mapmetricsgl from '@mapmetrics/mapmetrics-gl';
import '@mapmetrics/mapmetrics-gl/dist/mapmetrics-gl.css';
const MapComponent = () => {
const mapContainer = useRef(null);
const map = useRef(null);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
if (map.current) return;
const styleURL = 'YOUR_COMPLETE_STYLE_URL_HERE';
// Check if user forgot to replace the placeholder
if (styleURL === 'YOUR_COMPLETE_STYLE_URL_HERE' || styleURL.includes('YOUR_')) {
setError('⚠️ Please replace YOUR_COMPLETE_STYLE_URL_HERE with your actual style URL from https://portal.mapmetrics.org/');
setLoading(false);
return;
}
try {
const mapInstance = new mapmetricsgl.Map({
container: mapContainer.current,
style: styleURL,
center: [-74.006, 40.7128],
zoom: 12
});
// Handle successful map load
mapInstance.on('load', () => {
setLoading(false);
setError(null);
});
// Handle CRITICAL errors only (authentication, style loading failures)
// Don't show error UI for minor issues like tile loading failures
mapInstance.on('error', (e) => {
console.error('Map error:', e);
// Only show UI errors for critical failures that prevent map from working
// Ignore tile loading errors and other minor issues
const isCriticalError =
e.error?.status === 401 ||
e.error?.message?.includes('401') ||
(e.error?.message?.includes('Failed to fetch') && loading) ||
(e.sourceId === undefined && e.error); // Style loading errors have no sourceId
if (isCriticalError) {
setLoading(false);
// Authentication errors (401)
if (e.error?.message?.includes('401') || e.error?.status === 401) {
setError('❌ Invalid API token. Get a valid token from https://portal.mapmetrics.org/');
}
// Network/style URL errors during initial load
else if (e.error?.message?.includes('Failed to fetch') && loading) {
setError('❌ Cannot load map style. Check your style URL from https://portal.mapmetrics.org/');
}
// Generic critical error before map loads
else if (loading) {
setError('❌ Map failed to load. Check your style URL and token at https://portal.mapmetrics.org/');
}
// If map already loaded successfully, just log the error (don't break the UI)
else {
console.warn('Map error after successful load (non-critical):', e);
}
} else {
// Non-critical errors (tile loading, etc.) - just log, don't show error UI
console.warn('Non-critical map error:', e);
}
});
mapInstance.addControl(new mapmetricsgl.NavigationControl(), 'top-right');
map.current = mapInstance;
return () => {
if (map.current) {
map.current.remove();
map.current = null;
}
};
} catch (err) {
console.error('Map initialization error:', err);
setError('❌ Failed to initialize map. Check your style URL from https://portal.mapmetrics.org/');
setLoading(false);
}
}, []);
// Display error message if something went wrong
if (error) {
return (
<div style={{
height: '500px',
width: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#f8d7da',
color: '#721c24',
padding: '20px',
textAlign: 'center',
fontSize: '16px',
border: '2px solid #f5c6cb',
borderRadius: '8px',
fontFamily: 'system-ui, -apple-system, sans-serif'
}}>
<div>
<strong>{error}</strong>
<div style={{ marginTop: '10px', fontSize: '14px' }}>
Need help? Check the <a href="https://discord.com/invite/uRXQRfbb7d" style={{ color: '#721c24', textDecoration: 'underline' }}>Discord community</a>
</div>
</div>
</div>
);
}
// Optional: Show loading state
if (loading) {
return (
<div style={{
height: '500px',
width: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#f0f0f0',
color: '#666',
fontSize: '16px'
}}>
Loading map...
</div>
);
}
return (
<div
ref={mapContainer}
style={{
width: '100%',
height: '500px'
}}
/>
);
};
export default MapComponent;Complete Working Example
Here's a complete, copy-paste ready React component:
// MapComponent.jsx
import React, { useEffect, useRef, useState } from 'react';
import mapmetricsgl from '@mapmetrics/mapmetrics-gl';
import '@mapmetrics/mapmetrics-gl/dist/mapmetrics-gl.css';
const MapComponent = () => {
const mapContainer = useRef(null);
const map = useRef(null);
const [lng] = useState(-74.006);
const [lat] = useState(40.7128);
const [zoom] = useState(12);
useEffect(() => {
if (map.current) return;
// Get your complete style URL from https://portal.mapmetrics.org/
const styleURL = 'YOUR_COMPLETE_STYLE_URL_HERE';
map.current = new mapmetricsgl.Map({
container: mapContainer.current,
style: styleURL,
center: [lng, lat],
zoom: zoom
});
// Add controls
map.current.addControl(new mapmetricsgl.NavigationControl(), 'top-right');
map.current.addControl(new mapmetricsgl.FullscreenControl(), 'top-right');
return () => {
if (map.current) {
map.current.remove();
map.current = null;
}
};
}, [lng, lat, zoom]);
return (
<div style={{ width: '100%', height: '100vh' }}>
<div
ref={mapContainer}
style={{
width: '100%',
height: '100%'
}}
/>
</div>
);
};
export default MapComponent;Troubleshooting
Map doesn't load
- ✅ Check that you replaced
YOUR_COMPLETE_STYLE_URL_HEREwith actual URL from portal - ✅ Verify your API token is valid
- ✅ Check browser console for errors
Map container has no height
- ✅ Add
height: '500px'orheight: '100vh'to container style - ✅ Ensure parent container also has height
npm install fails
- ✅ Make sure you're using the correct package:
@mapmetrics/mapmetrics-gl - ✅ Run
npm install @mapmetrics/mapmetrics-gl - ✅ Check NPM registry: https://www.npmjs.com/package/@mapmetrics/mapmetrics-gl
Adding Features
Add a Marker
// After map initialization
const marker = new mapmetricsgl.Marker()
.setLngLat([-74.006, 40.7128])
.addTo(map.current);Add a Popup
const popup = new mapmetricsgl.Popup()
.setLngLat([-74.006, 40.7128])
.setHTML('<h3>Hello World!</h3>')
.addTo(map.current);Handle Click Events
map.current.on('click', (e) => {
console.log('Map clicked at:', e.lngLat);
});Next Steps
- Add Markers - Display points of interest
- Add Routing - Turn-by-turn directions
- Geocoding - Search for locations
Common Pitfalls
1. Do not use map.on('error') for fatal error detection
The error event fires for non-fatal issues such as missing tiles, failed sprites, or slow network requests. Using it to show a fatal error screen will cause the error UI to appear even when the map loads successfully.
Use a load timeout instead:
const timeout = setTimeout(() => {
// map failed to load within 15 seconds
}, 15000);
map.on('load', () => clearTimeout(timeout));2. React 18 StrictMode
In development, React StrictMode runs every effect twice (mount → cleanup → mount). This causes map.remove() to be called on the first mount before the map is recreated on the second mount. This is expected behaviour. Make sure your cleanup function fully removes the map instance so the second mount can initialise cleanly:
return () => {
if (map.current) {
map.current.remove();
map.current = null; // ← required so the second mount starts fresh
}
};3. Never unmount the map container div on error
If your error state causes the map container <div> to be removed from the DOM, the map ref becomes null and the map cannot recover. Always keep the container div mounted and display errors as an overlay on top of it.
// ❌ Wrong — removes the container div
if (error) return <div>Something went wrong</div>;
// ✅ Correct — overlay on top of the container
return (
<>
<div ref={mapContainer} style={{ width: '100%', height: '500px' }} />
{error && <div className="error-overlay">Something went wrong</div>}
</>
);Need Help? Join our Discord community or check more examples