Skip to content

React Map Integration

This guide shows you how to integrate MapMetrics into a React application.

Prerequisites

Quick Start

1. Create React App

bash
npx create-react-app my-map-app
cd my-map-app

2. Install MapMetrics GL Package

bash
npm install @mapmetrics/mapmetrics-gl

Package Details:

3. Create Map Component

Create a new file src/MapComponent.jsx:

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:

jsx
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:

css
.App {
  text-align: center;
  padding: 20px;
}

h1 {
  margin-bottom: 20px;
}

6. Run Your App

bash
npm start

Your 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:

javascript
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:

jsx
// Option 1: Inline style
<div ref={mapContainer} style={{ height: '500px', width: '100%' }} />

// Option 2: CSS class
<div ref={mapContainer} className="map-container" />
css
.map-container {
  height: 500px;
  width: 100%;
}

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:

jsx
// 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:

jsx
// 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_HERE with actual URL from portal
  • ✅ Verify your API token is valid
  • ✅ Check browser console for errors

Map container has no height

  • ✅ Add height: '500px' or height: '100vh' to container style
  • ✅ Ensure parent container also has height

npm install fails

Adding Features

Add a Marker

jsx
// After map initialization
const marker = new mapmetricsgl.Marker()
  .setLngLat([-74.006, 40.7128])
  .addTo(map.current);

Add a Popup

jsx
const popup = new mapmetricsgl.Popup()
  .setLngLat([-74.006, 40.7128])
  .setHTML('<h3>Hello World!</h3>')
  .addTo(map.current);

Handle Click Events

jsx
map.current.on('click', (e) => {
  console.log('Map clicked at:', e.lngLat);
});

Next Steps


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:

javascript
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:

jsx
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.

jsx
// ❌ 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