Learn

GeoJSON with React

React has no built-in GeoJSON rendering. The two most common libraries for adding a map to a React app are react-leaflet and react-map-gl. Both accept GeoJSON directly.

Load GeoJSON with fetch

The simplest approach is a custom hook that fetches a GeoJSON file and stores it in state. The url is the dependency — the effect re-runs whenever it changes.

import { useState, useEffect } from 'react'
import type { FeatureCollection } from 'geojson'

function useGeoJSON(url: string) {
  const [data, setData] = useState<FeatureCollection | null>(null)

  useEffect(() => {
    fetch(url)
      .then(r => r.json())
      .then(setData)
  }, [url])

  return data
}

Call it at the component level: const data = useGeoJSON('/features.geojson'). It returns null on the first render (before the fetch resolves), so guard against null before passing data to a map layer.

Render with react-leaflet

react-leaflet exposes a <GeoJSON> component that wraps Leaflet's L.geoJSON(). Pass your FeatureCollection as the data prop.

import { MapContainer, TileLayer, GeoJSON } from 'react-leaflet'
import { useMemo } from 'react'

export function MyMap({ data }: { data: FeatureCollection }) {
  const stableData = useMemo(() => data, [data])

  return (
    <MapContainer center={[51.5074, -0.1276]} zoom={12} style={{ height: 400 }}>
      <TileLayer url="https://tile.openstreetmap.org/{z}/{x}/{y}.png" />
      <GeoJSON key={JSON.stringify(stableData)} data={stableData} />
    </MapContainer>
  )
}

Note: react-leaflet's <GeoJSON> doesn't update when data changes — pass a new key to force remount when features change.

Render with react-map-gl

react-map-gl uses a source/layer model that mirrors Mapbox GL JS. A <Source> holds the data; one or more <Layer> components describe how to render it.

import Map, { Source, Layer } from 'react-map-gl'
import type { FillLayer } from 'react-map-gl'

const layerStyle: FillLayer = {
  id: 'zones-fill',
  type: 'fill',
  paint: { 'fill-color': '#3388ff', 'fill-opacity': 0.4 },
}

export function MyMap({ data }: { data: FeatureCollection }) {
  return (
    <Map
      initialViewState={{ longitude: -0.1276, latitude: 51.5074, zoom: 12 }}
      style={{ height: 400 }}
      mapStyle="mapbox://styles/mapbox/light-v11"
    >
      <Source type="geojson" data={data}>
        <Layer {...layerStyle} />
      </Source>
    </Map>
  )
}

Defining layerStyle outside the component avoids a new object reference on every render. For dynamic styles that depend on state, wrap in useMemo.

Track selected feature

Click handlers in react-leaflet attach via onEachFeature. Store the selected Feature in state and derive the style from it.

import { useState } from 'react'
import type { Feature } from 'geojson'

export function MyMap({ data }: { data: FeatureCollection }) {
  const [selected, setSelected] = useState<Feature | null>(null)

  return (
    <MapContainer center={[51.5074, -0.1276]} zoom={12} style={{ height: 400 }}>
      <TileLayer url="https://tile.openstreetmap.org/{z}/{x}/{y}.png" />
      <GeoJSON
        data={data}
        onEachFeature={(feature, layer) => {
          layer.on('click', () => setSelected(feature))
        }}
        style={(feature) => ({
          fillColor: feature?.id === selected?.id ? '#ff6b35' : '#3388ff',
          fillOpacity: 0.5,
        })}
      />
      {selected && <p>Selected: {selected.properties?.name}</p>}
    </MapContainer>
  )
}

The style callback runs on every render, so the selected feature gets the highlight colour immediately on state change.

Watch out: Passing a new object reference for data on every render causes react-leaflet's <GeoJSON> to re-render the entire layer every tick. Wrap static data in useMemo or define it outside the component. For react-map-gl, <Source> handles reference changes more gracefully but useMemo is still the right pattern.

Editing GeoJSON features in React

Rendering GeoJSON with react-leaflet or react-map-gl is one part of the story. Drawing new features, moving vertices, cutting holes, and boolean operations require an editing layer on top of the map.

@element-editor/react is an open-source MIT library with a drop-in <ElementEditor /> component that brings its own MapLibre map (or a Leaflet map, from @element-editor/react/leaflet) plus the full editing UI. It takes your FeatureCollection as initialData and returns the updated FeatureCollection via onChange. To add editing to a map you already render with react-leaflet or MapLibre, use @element-editor/adapter-leaflet or @element-editor/adapter-maplibre.

Read the quickstart


For Leaflet-specific patterns, see GeoJSON with Leaflet.js. For Mapbox GL JS patterns, see GeoJSON with Mapbox GL JS.

Continue reading

Mike Acler

Mike Acler

Design engineer and creator of Element Editor, an open-source map drawing and GeoJSON editing library for React. Writes about web cartography and GIS tooling.