Skip to content

React Quickstart

Install

bash
npm install maplibre-gl react-map-gl

react-map-gl is the idiomatic React wrapper. You can also use raw MapLibre.

Component (react-map-gl v8)

jsx
import { Map, Marker, NavigationControl } from 'react-map-gl/maplibre';
import 'maplibre-gl/dist/maplibre-gl.css';

export default function MapView() {
  return (
    <Map
      initialViewState={{ longitude: 28.04, latitude: -26.20, zoom: 11 }}
      style={{ width: '100%', height: '100vh' }}
      mapStyle={`https://api.mapsfordevs.com/styles/standard.json?key=${import.meta.env.VITE_MFD_PUB_KEY}`}
    >
      <NavigationControl position="top-right" />
      <Marker longitude={28.04} latitude={-26.20} color="#ef4444" />
    </Map>
  );
}

Raw MapLibre with hooks

jsx
import { useEffect, useRef } from 'react';
import maplibregl from 'maplibre-gl';
import 'maplibre-gl/dist/maplibre-gl.css';

export function useMapLibre(opts) {
  const ref = useRef(null);
  const mapRef = useRef(null);

  useEffect(() => {
    if (!ref.current) return;
    const map = new maplibregl.Map({ container: ref.current, ...opts });
    mapRef.current = map;
    return () => { map.remove(); mapRef.current = null; };
  }, []);

  return { ref, map: mapRef };
}

export default function MapView() {
  const { ref } = useMapLibre({
    style: `https://api.mapsfordevs.com/styles/standard.json?key=${import.meta.env.VITE_MFD_PUB_KEY}`,
    center: [28.04, -26.20],
    zoom: 11
  });
  return <div ref={ref} style={{ width: '100%', height: '100vh' }} />;
}

Language picker

jsx
import { useEffect } from 'react';

function applyLang(map, lang) {
  ['place_label', 'transportation_name', 'poi_label'].forEach(layer => {
    map.setLayoutProperty(layer, 'text-field', [
      'coalesce', ['get', `name:${lang}`], ['get', 'name:latin'], ['get', 'name']
    ]);
  });
}

export function LanguagePicker({ map, value, onChange }) {
  useEffect(() => {
    if (!map) return;
    if (map.isStyleLoaded()) applyLang(map, value);
    else map.once('styledata', () => applyLang(map, value));
  }, [map, value]);

  return (
    <select value={value} onChange={e => onChange(e.target.value)}>
      <option value="en">English</option>
      <option value="de">Deutsch</option>
      <option value="ja">日本語</option>
      <option value="ar">العربية</option>
    </select>
  );
}

Geocoding hook

jsx
import { useState, useCallback } from 'react';

export function useGeocode(apiKey) {
  const [results, setResults] = useState([]);
  const [loading, setLoading] = useState(false);

  const search = useCallback(async (q) => {
    setLoading(true);
    try {
      const r = await fetch(
        `https://api.mapsfordevs.com/geocode/forward?q=${encodeURIComponent(q)}&limit=5`,
        { headers: { Authorization: `Bearer ${apiKey}` } }
      );
      const body = await r.json();
      if (body.ok) setResults(body.items);
    } finally {
      setLoading(false);
    }
  }, [apiKey]);

  return { results, loading, search };
}

TypeScript

react-map-gl ships its own types. For raw MapLibre:

tsx
const mapRef = useRef<maplibregl.Map | null>(null);

SSR (Next.js)

MapLibre is browser-only — wrap in dynamic import with ssr: false. See nextjs.md.