Leaflet-Geoman with React and TypeScript

Published Apr 17, 2024
Updated
Leaflet-Geoman with React and TypeScript
Niclas Priess
Niclas Priess
Head of Sales

This guide adds Leaflet-Geoman Free to a React application. It uses Vite for the starter and works with existing React projects too.

Updating an older Create React App project? This page keeps its original URL. React deprecated Create React App; use a current framework or build tool for new projects. The map component below can also replace the integration in an existing CRA app. For server-rendered applications, follow the Next.js guide.

Create the project and install Geoman

npm create vite@latest geoman-react -- --template react-ts
cd geoman-react
npm install
npm install [email protected] @geoman-io/[email protected]
npm install --save-dev @types/leaflet

The example component is exercised with React 19, Leaflet 1.9.4 and Geoman Free 2.20.2. It uses Leaflet directly; a React-Leaflet wrapper is optional.

Create the map component

Save this as src/GeomanMap.tsx. The map is created after the container mounts. Removing it in the effect cleanup prevents duplicate controls, leftover listeners and “Map container is already initialized” errors during remounts.

import React, { useEffect, useRef, useState } from "react"
import * as L from "leaflet"
import "@geoman-io/leaflet-geoman-free"
import "leaflet/dist/leaflet.css"
import "@geoman-io/leaflet-geoman-free/dist/leaflet-geoman.css"

export default function GeomanMap() {
  const container = useRef<HTMLDivElement>(null)
  const features = useRef<L.FeatureGroup | null>(null)
  const [geojson, setGeojson] = useState("")

  useEffect(() => {
    if (!container.current) return
    const map = L.map(container.current).setView([51.505, -0.09], 13)
    // A token-free example basemap. Choose a production tile provider for your app.
    L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", {
      attribution:
        '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
    }).addTo(map)
    const drawn = L.featureGroup().addTo(map)
    features.current = drawn
    map.pm.addControls({
      drawMarker: false,
      drawCircle: false,
      drawCircleMarker: false,
    })
    const onCreate: L.PM.CreateEventHandler = (event) =>
      drawn.addLayer(event.layer)
    const onRemove: L.PM.RemoveEventHandler = (event) =>
      drawn.removeLayer(event.layer)
    map.on("pm:create", onCreate)
    map.on("pm:remove", onRemove)

    // Cleanup also makes React StrictMode's development remount safe.
    return () => {
      map.off("pm:create", onCreate)
      map.off("pm:remove", onRemove)
      map.remove()
      features.current = null
    }
  }, [])

  return (
    <section>
      <div ref={container} style={{ height: "60vh", minHeight: 320 }} />
      <button
        type="button"
        onClick={() =>
          setGeojson(
            JSON.stringify(features.current?.toGeoJSON(), null, 2) ?? "",
          )
        }
      >
        Export GeoJSON
      </button>
      <pre aria-label="Exported GeoJSON">{geojson}</pre>
    </section>
  )
}

The feature group contains layers created through Geoman. Editing changes those same layers, so the export button reads their current geometry. This starter disables circles because GeoJSON has no native circle geometry; use polygons when portable area geometry is required.

Render it in your application

Replace src/App.tsx with:

import GeomanMap from "./GeomanMap"

export default function App() {
  return (
    <main>
      <h1>Draw a map feature</h1>
      <GeomanMap />
    </main>
  )
}

Remove Vite's demo layout styles if they constrain the map width. Keep a nonzero height on the map container. Run npm run dev, draw a polygon with the toolbar and click Export GeoJSON to inspect the feature collection.

Common integration problems

  • Missing toolbar icons: import Geoman CSS as well as Leaflet CSS.
  • Map already initialized: create it inside an effect and call map.remove() in cleanup. Do not disable StrictMode to hide a lifecycle problem.
  • window is not defined: Leaflet was imported by server code. Follow the browser-only boundary in the Next.js guide.
  • Empty export: export the layers your drawing event adds to the feature group, rather than tile layers or every layer on the map.

Continue with the Leaflet documentation, explore the interactive demo, or compare Free and Pro features.

Want more geo-editing insights?

Get tips, tutorials, and product updates delivered to your inbox.

No spam. Unsubscribe anytime.


Keywords

Get geo-editing tips & product updates

We'll email you updates. Unsubscribe anytime. Privacy Policy

© 2026 Geoman. All rights reserved.

Summarize with AI