mrkeyoor.com_
Wed 23 Sept 00:32 UTC
npmWeb Frontendupdated 22 Sept 2026

react-leaflet review

React Leaflet 5 wraps Leaflet 1.9 maps, layers, markers, popups, controls, and events in React 19 components and hooks. Our browser build was 170.2 KB minified and 49.8 KB gzipped. React manages component lifecycle while Leaflet owns the map objects and map DOM. Version 5 moves the peer floor to React 19 and removes `LeafletProvider` from the core package. It supplies no tiles, geocoding, routing, offline store, or server-rendered map.

Verdict

React Leaflet 5 installed in 2 seconds and built to 170.2 KB minified in our sandbox, while direct `require()` and ESM `import` both failed under Node.js v22.23.2. Install it for client-only React 19 maps built on Leaflet; walk away for SSR, React 18, an automatic controlled-prop model, or an unapproved Hippocratic-2.1 license.

We installed it

Lab card: what happened when we installed react-leafletScreenshot of react-leaflet documentation
Install✓ · 2s6 packages on disk · 12 MB
ImportESM import fails · require() fails · ESM package with exports map
Browser49.8 KBgzipped (170.2 KB minified), bundled with esbuild
TypesTypeScript types bundled
Known vulns00 critical · 0 high · 0 moderate · 0 low (npm audit)

Answers from our run

Does react-leaflet install cleanly?

Yes. In a fresh container with an empty cache, npm install react-leaflet finished in 2 seconds, leaving 6 packages and 12 MB on disk. npm audit reported no known vulnerabilities.

How much does react-leaflet add to a browser bundle?

49.8 KB gzipped (170.2 KB minified) when the whole package is bundled for the browser with esbuild. Importing only part of it is usually smaller.

Does react-leaflet work with both ESM and CommonJS?

Neither plain import nor require succeeded in our sandbox, so it needs a bundler or extra setup.

Does react-leaflet include TypeScript types?

Yes, type declarations ship inside the package, so no @types install is needed.

react-leaflet or react-map-gl: which should you use?

react-map-gl: Use it for React bindings around MapLibre or Mapbox GL when vector styles, bearing, and pitch matter. React Leaflet 5 installed in 2 seconds and built to 170.2 KB minified in our sandbox, while direct require() and ESM import both failed under Node.js v22.23.2.

When should you not use react-leaflet?

Server rendering must execute the map module: the official introduction says Leaflet touches the DOM while loading and React Leaflet is incompatible with SSR

API stability3/5The central component names and `useMap` hooks remain recognizable, though recent majors contain real breaks. Version 5 requires React 19 and removes `LeafletProvider` from the core package. Version 4 had already removed CommonJS and UMD builds, `MapConsumer`, `useMapElement`, `whenCreated`, and older popup lifecycle props. Most creation props are intentionally immutable, so React state does not automatically update every Leaflet option.
Docs4/5The versioned documentation explains the React and Leaflet responsibility split, DOM and SSR limit, CSS requirement, container sizing, immutable options, hooks, events, panes, external state, and the mutable fields of each component. Working examples cover bounds, layers, popups, GeoJSON, and event handlers. Many low-level options still link out to Leaflet's reference, and users must combine both sites to understand lifecycle plus map behavior.
Maintenance3/5Version 5.0.0 shipped on 2024-12-14 and GitHub shows the last repository push on 2025-12-31. GitHub currently counts 48 open issues and pull requests, with support questions directed to Stack Overflow. The wrapper tracks stable Leaflet rather than releasing often, so a quiet cadence is understandable. Still, React-major compatibility and client-build breakage can wait longer than teams expect from a package with millions of weekly installs.
Ecosystem4/5The npm API measured 3,578,459 downloads for the latest week, and the repository has 5,597 stars. React Leaflet connects React 19 to Leaflet 1.9's tile providers, plugins, GeoJSON, controls, markers, and vector layers. Included declarations and standard hooks make extension possible. Plugin quality varies, React adapters are not assured, and Hippocratic-2.1 can prevent adoption where policy requires an OSI-approved permissive license.

Use it if

  • A React 19 application needs classic raster tiles, markers, popups, GeoJSON, and vector overlays
  • Leaflet's plugin and provider ecosystem is already part of the mapping plan
  • Imperative camera changes through `useMap`, refs, `setView`, or `fitBounds` fit the component design
  • A DOM-based map is enough and WebGL pitch, globe views, or large vector-tile scenes are unnecessary
Skip it if

Setup reality

Our fresh install of react-leaflet 5.0.0 took 2 seconds and left 6 packages using 12 MB on disk. The package itself has one direct dependency and 3 peers, bundles TypeScript declarations, and produced 0 known audit vulnerabilities. Its unpacked size is 220 KB under Hippocratic-2.1.

Install matching peers for React 19, React DOM 19, and Leaflet 1.9. Import leaflet/dist/leaflet.css and assign a real height to MapContainer; missing either commonly leaves a blank or broken map box. Leaflet declarations may still require @types/leaflet. Choose a tile provider, preserve required attribution, follow its traffic policy, and supply any API token through your own configuration.

The package declares ESM and has an exports map, yet both require() and ESM import failed in our direct Node.js v22.23.2 checks. The browser-oriented esbuild path succeeded at 170.2 KB minified and 49.8 KB gzipped. Keep imports inside a client-only boundary. In Next.js, dynamically load the map with server rendering disabled so a server-evaluated module never loads Leaflet.

MapContainer uses center, zoom, bounds, and other creation options only on first render. Move the camera later through useMap or a ref. Component documentation marks the smaller set of mutable props; changing other values may require a new React key to recreate the layer. Default marker images can break when a bundler relocates Leaflet assets, so import image files and construct an explicit Icon. Review Hippocratic-2.1 before shipping because its obligations differ from MIT or BSD terms.

Patterns

Mount a map with visible tiles render-tile-map

import 'leaflet/dist/leaflet.css'
import { MapContainer, TileLayer } from 'react-leaflet'

export function Map() {
  return <MapContainer center={[51.505, -0.09]} zoom={13} style={{ height: 420 }}>
    <TileLayer
      attribution='&copy; OpenStreetMap contributors'
      url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
    />
  </MapContainer>
}

Leaflet CSS and a nonzero height are both required. Check the tile provider's production traffic and attribution policy.

Attach a popup to a marker add-marker-popup

import { Marker, Popup } from 'react-leaflet'

<Marker position={[51.505, -0.09]}>
  <Popup>Central London</Popup>
</Marker>

Render this below `MapContainer` so context can supply the map. Default marker image URLs may need explicit bundler handling.

React to clicks and location results handle-map-events

function LocationMarker() {
  const [position, setPosition] = useState(null)
  const map = useMapEvents({
    click() { map.locate() },
    locationfound(event) {
      setPosition(event.latlng)
      map.flyTo(event.latlng, map.getZoom())
    },
    locationerror(error) { console.error(error) },
  })
  return position ? <Marker position={position} /> : null
}

Browser geolocation generally needs HTTPS and user permission. Handle `locationerror` so denial does not look like a dead map.

Move the camera after mount update-map-view

function ViewController({ center, zoom }) {
  const map = useMap()
  useEffect(() => {
    map.setView(center, zoom)
  }, [map, center, zoom])
  return null
}

Changing `MapContainer` center or zoom later does nothing because those creation options are immutable.

Fit the viewport around data fit-feature-bounds

function FitBounds({ bounds }) {
  const map = useMap()
  useEffect(() => {
    if (bounds) map.fitBounds(bounds, { padding: [24, 24] })
  }, [map, bounds])
  return null
}

Memoize `bounds` when possible. A fresh array each render can repeatedly reset a user's camera.

Style and label GeoJSON render-geojson

<GeoJSON
  data={featureCollection}
  style={(feature) => ({
    color: feature?.properties?.selected ? '#dc2626' : '#2563eb',
    weight: 2, fillOpacity: 0.25,
  })}
  onEachFeature={(feature, layer) =>
    layer.bindTooltip(feature.properties?.name ?? 'Unnamed area')
  }
/>

Version 5 does not mark GeoJSON `data` mutable. Change the component key when replacing the full collection.

Offer two tile sources switch-base-layers

<LayersControl position="topright">
  <LayersControl.BaseLayer checked name="Street">
    <TileLayer attribution='&copy; OpenStreetMap contributors' url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
  </LayersControl.BaseLayer>
  <LayersControl.BaseLayer name="Humanitarian">
    <TileLayer attribution='&copy; OpenStreetMap contributors, Tiles style by HOT' url="https://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png" />
  </LayersControl.BaseLayer>
</LayersControl>

Example tile URLs are external services with separate capacity and attribution terms, not bundled React Leaflet assets.

Import marker images through the bundler use-custom-marker-icon

import { Icon } from 'leaflet'
import markerUrl from './marker.png'
import shadowUrl from './marker-shadow.png'

const icon = new Icon({
  iconUrl: markerUrl, shadowUrl,
  iconSize: [25, 41], iconAnchor: [12, 41],
})
<Marker position={[51.505, -0.09]} icon={icon} />

An explicit `Icon` avoids broken defaults after a bundler moves Leaflet's package images.

Store a marker's dragged position track-dragged-marker

function DraggableMarker() {
  const [position, setPosition] = useState([51.505, -0.09])
  const ref = useRef(null)
  const eventHandlers = useMemo(() => ({
    dragend() { if (ref.current) setPosition(ref.current.getLatLng()) },
  }), [])
  return <Marker ref={ref} draggable position={position} eventHandlers={eventHandlers} />
}

The ref exposes a Leaflet `Marker`, not a DOM element. Memoizing handlers avoids replacing listeners each render.

Draw a radius and route draw-vector-layers

<Circle
  center={[51.505, -0.09]} radius={250}
  pathOptions={{ color: '#7c3aed', fillOpacity: 0.2 }}
/>
<Polyline
  positions={[[51.50, -0.10], [51.505, -0.09], [51.51, -0.08]]}
  pathOptions={{ color: '#0f766e', weight: 4 }}
/>

`pathOptions` is mutable. Check the component reference before assuming another Leaflet constructor option updates.

Control vector stacking order set-layer-pane

<Pane name="alerts" style={{ zIndex: 650, pointerEvents: 'none' }}>
  <Circle
    center={[51.505, -0.09]} radius={500}
    pathOptions={{ color: '#dc2626', fillOpacity: 0.15 }}
  />
</Pane>

Pane names share one map namespace. `pointerEvents: 'none'` lets gestures reach the map below the overlay.

Reset the camera from a button access-map-ref

function MapWithReset() {
  const mapRef = useRef(null)
  return <>
    <button onClick={() => mapRef.current?.setView([51.505, -0.09], 13)}>Reset</button>
    <MapContainer ref={mapRef} center={[51.505, -0.09]} zoom={13} style={{ height: 420 }}>
      {/* layers */}
    </MapContainer>
  </>
}

Avoid multiple components issuing camera commands at once. Give one controller ownership of map movement.

Alternatives

PackageRegistryPick it when
react-map-glnpmUse it for React bindings around MapLibre or Mapbox GL when vector styles, bearing, and pitch matter
maplibre-glnpmUse it directly for an imperative WebGL vector map without a React component abstraction
leafletnpmUse Leaflet alone when React lifecycle wrappers add no value or another framework owns the UI

More web frontend guides

postcss · react · react-dom · tailwindcss · htmlparser2 · tailwind-merge · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.