Ctrl + k

or run

tessl search
Log in

Version

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
npmpkg:npm/react-leaflet@5.0.x

docs

index.md
tile.json

tessl/npm-react-leaflet

tessl install tessl/npm-react-leaflet@5.0.3

React components for Leaflet maps

layer-groups.mddocs/reference/

Layer Groups

Components for organizing and grouping layers together, including support for GeoJSON data and custom rendering panes.

Related Documentation

  • Markers and Popups - Individual markers to group
  • Vector Shapes - Shapes to group together
  • Controls - LayersControl for user-controlled groups
  • Map Container - Pane management

Capabilities

LayerGroup Component

Groups multiple layers together for collective management.

/**
 * Component for grouping multiple layers
 * @param props - Layer group properties
 */
const LayerGroup: FunctionComponent<LayerGroupProps>;

interface LayerGroupProps extends LayerOptions, EventedProps {
  /** Child layers */
  children?: ReactNode;
}

Usage Example:

import { MapContainer, TileLayer, LayerGroup, Circle, Marker } from "react-leaflet";

function Map() {
  return (
    <MapContainer center={[51.505, -0.09]} zoom={13}>
      <TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
      <LayerGroup>
        <Circle center={[51.505, -0.09]} radius={200} />
        <Circle center={[51.51, -0.1]} radius={200} />
        <Marker position={[51.505, -0.09]} />
      </LayerGroup>
    </MapContainer>
  );
}

FeatureGroup Component

Extended layer group with shared styling and popup/tooltip support.

/**
 * Component for grouping layers with shared styling
 * @param props - Feature group properties
 */
const FeatureGroup: FunctionComponent<FeatureGroupProps>;

interface FeatureGroupProps extends LayerGroupProps, PathProps {}

Usage Example:

<FeatureGroup pathOptions={{ color: "purple" }}>
  <Popup>Shared popup for all features</Popup>
  <Circle center={[51.505, -0.09]} radius={200} />
  <Circle center={[51.51, -0.1]} radius={200} />
  <Polyline positions={[[51.505, -0.09], [51.51, -0.1]]} />
</FeatureGroup>

GeoJSON Component

Renders GeoJSON data as map layers.

/**
 * Component for rendering GeoJSON data
 * @param props - GeoJSON properties
 */
const GeoJSON: FunctionComponent<GeoJSONProps>;

interface GeoJSONProps extends GeoJSONOptions, LayerGroupProps, PathProps {
  /** GeoJSON data to render (required) */
  data: GeoJsonObject;
}

Usage Example:

const geojsonFeature = {
  type: "Feature",
  properties: {
    name: "Sample Location",
    popupContent: "This is where the sample happened.",
  },
  geometry: {
    type: "Point",
    coordinates: [-0.09, 51.505],
  },
};

<GeoJSON data={geojsonFeature} />

With Styling Function:

const geojsonData = {
  type: "FeatureCollection",
  features: [
    {
      type: "Feature",
      properties: { type: "park" },
      geometry: {
        type: "Polygon",
        coordinates: [[[−0.09, 51.505], [−0.1, 51.51], [−0.08, 51.51], [−0.09, 51.505]]],
      },
    },
  ],
};

<GeoJSON
  data={geojsonData}
  style={(feature) => {
    switch (feature.properties.type) {
      case "park":
        return { color: "green", fillColor: "lightgreen" };
      case "water":
        return { color: "blue", fillColor: "lightblue" };
      default:
        return { color: "gray" };
    }
  }}
/>

With Point Styling and Interaction:

<GeoJSON
  data={geojsonData}
  pointToLayer={(feature, latlng) => {
    return L.circleMarker(latlng, { radius: 8, fillColor: "red" });
  }}
  onEachFeature={(feature, layer) => {
    if (feature.properties && feature.properties.name) {
      layer.bindPopup(feature.properties.name);
    }
  }}
/>

Pane Component

Custom rendering pane for controlling z-index and rendering order.

/**
 * Component for creating custom rendering panes
 * @param props - Pane properties
 * @param ref - Optional ref to access the HTML element
 */
const Pane: ForwardRefExoticComponent<PaneProps & RefAttributes<PaneRef>>;

interface PaneProps {
  /** Pane name (required) */
  name: string;
  /** Child layers to render in this pane */
  children?: ReactNode;
  /** CSS class for the pane */
  className?: string;
  /** Inline styles for the pane */
  style?: CSSProperties;
  /** Parent pane name */
  pane?: string;
}

type PaneRef = HTMLElement | null;

Usage Example:

<MapContainer center={[51.505, -0.09]} zoom={13}>
  <TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
  <Pane name="custom-pane" style={{ zIndex: 650 }}>
    <Circle center={[51.505, -0.09]} radius={200} />
    <Marker position={[51.51, -0.1]} />
  </Pane>
</MapContainer>

Nested Panes:

<Pane name="base-pane" style={{ zIndex: 400 }}>
  <Pane name="nested-pane" style={{ zIndex: 450 }}>
    <Marker position={[51.505, -0.09]} />
  </Pane>
</Pane>

Types

LayerOptions

// From Leaflet
interface LayerOptions {
  /** Map pane where the layer will be added */
  pane?: string;
  /** Attribution text */
  attribution?: string;
}

PathProps

interface PathProps extends InteractiveLayerProps {
  /** Path styling options */
  pathOptions?: PathOptions;
}

GeoJSONOptions

// From Leaflet
interface GeoJSONOptions extends LayerOptions {
  /** Function to convert Point features to Leaflet layers */
  pointToLayer?: (geoJsonPoint: Feature<Point>, latlng: LatLng) => Layer;
  /** Function to style features */
  style?: StyleFunction;
  /** Function called on each created feature layer */
  onEachFeature?: (feature: Feature, layer: Layer) => void;
  /** Function to filter features */
  filter?: (geoJsonFeature: Feature) => boolean;
  /** Coordinate system for GeoJSON coordinates */
  coordsToLatLng?: (coords: [number, number] | [number, number, number]) => LatLng;
  /** Whether to make GeoJSON layers interactive */
  markersInheritOptions?: boolean;
}

type StyleFunction = (feature?: Feature) => PathOptions;

GeoJsonObject Types

// From GeoJSON spec
type GeoJsonObject =
  | Point
  | MultiPoint
  | LineString
  | MultiLineString
  | Polygon
  | MultiPolygon
  | GeometryCollection
  | Feature
  | FeatureCollection;

interface Feature<G = Geometry, P = any> {
  type: "Feature";
  geometry: G;
  properties: P;
  id?: string | number;
}

interface FeatureCollection<G = Geometry, P = any> {
  type: "FeatureCollection";
  features: Array<Feature<G, P>>;
}

// Coordinate order in GeoJSON: [longitude, latitude]
// Note: This is opposite of Leaflet's [latitude, longitude]
type GeoJSONPosition = [number, number] | [number, number, number];

Important Notes

  1. LayerGroup vs FeatureGroup:

    • LayerGroup: Simple container for layers
    • FeatureGroup: Extends LayerGroup with shared styling and popup/tooltip support
  2. Conditional Rendering: Toggle layer groups visibility:

    {showMarkers && (
      <LayerGroup>
        <Marker position={pos1} />
        <Marker position={pos2} />
      </LayerGroup>
    )}
  3. GeoJSON Data Updates: GeoJSON component re-renders when data prop changes:

    <GeoJSON data={currentGeoJsonData} />
  4. GeoJSON Styling: Use style prop for dynamic styling based on feature properties:

    style={(feature) => ({
      color: feature.properties.color,
      weight: feature.properties.importance * 2,
    })}
  5. Point Customization: Use pointToLayer to customize how Point features are rendered:

    pointToLayer={(feature, latlng) => {
      return L.marker(latlng, {
        icon: getIconForFeature(feature),
      });
    }}
  6. Feature Interaction: Use onEachFeature to add popups, tooltips, or event handlers:

    onEachFeature={(feature, layer) => {
      layer.bindPopup(feature.properties.name);
      layer.on({
        click: () => console.log(feature.properties),
      });
    }}
  7. GeoJSON Filtering: Filter which features to display:

    filter={(feature) => feature.properties.visible === true}
  8. Pane Z-Index: Default pane z-indexes:

    • mapPane: 0
    • tilePane: 200
    • overlayPane: 400
    • shadowPane: 500
    • markerPane: 600
    • tooltipPane: 650
    • popupPane: 700
  9. Custom Pane Styling: Control layer rendering order with custom panes:

    <Pane name="labels" style={{ zIndex: 650 }}>
      {/* Layers that should appear above others */}
    </Pane>

Examples

Toggling Layer Groups

function MapWithTogglableLayers() {
  const [showMarkers, setShowMarkers] = useState(true);
  const [showCircles, setShowCircles] = useState(true);

  return (
    <>
      <button onClick={() => setShowMarkers(!showMarkers)}>
        Toggle Markers
      </button>
      <button onClick={() => setShowCircles(!showCircles)}>
        Toggle Circles
      </button>
      <MapContainer center={[51.505, -0.09]} zoom={13}>
        <TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
        {showMarkers && (
          <LayerGroup>
            <Marker position={[51.505, -0.09]} />
            <Marker position={[51.51, -0.1]} />
          </LayerGroup>
        )}
        {showCircles && (
          <LayerGroup>
            <Circle center={[51.505, -0.09]} radius={200} />
            <Circle center={[51.51, -0.1]} radius={200} />
          </LayerGroup>
        )}
      </MapContainer>
    </>
  );
}

GeoJSON with Dynamic Styling

function GeoJSONMap({ geojsonData }) {
  return (
    <MapContainer center={[51.505, -0.09]} zoom={13}>
      <TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
      <GeoJSON
        data={geojsonData}
        style={(feature) => {
          const magnitude = feature.properties.magnitude;
          return {
            color: magnitude > 5 ? "red" : magnitude > 3 ? "orange" : "yellow",
            weight: magnitude,
            fillOpacity: 0.5,
          };
        }}
        onEachFeature={(feature, layer) => {
          layer.bindPopup(
            `<h3>${feature.properties.name}</h3><p>Magnitude: ${feature.properties.magnitude}</p>`
          );
        }}
      />
    </MapContainer>
  );
}

Custom Point Markers from GeoJSON

import L from "leaflet";

function GeoJSONWithCustomMarkers({ pointsData }) {
  return (
    <GeoJSON
      data={pointsData}
      pointToLayer={(feature, latlng) => {
        const icon = L.divIcon({
          className: "custom-marker",
          html: `<div style="background-color: ${feature.properties.color}; width: 20px; height: 20px; border-radius: 50%;"></div>`,
        });
        return L.marker(latlng, { icon });
      }}
      onEachFeature={(feature, layer) => {
        layer.bindTooltip(feature.properties.label, { permanent: true });
      }}
    />
  );
}

Layered Panes for Complex Rendering

function MultiPaneMap() {
  return (
    <MapContainer center={[51.505, -0.09]} zoom={13}>
      <TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />

      <Pane name="background-shapes" style={{ zIndex: 350 }}>
        <Circle center={[51.505, -0.09]} radius={500} pathOptions={{ color: "blue" }} />
      </Pane>

      <Pane name="foreground-markers" style={{ zIndex: 650 }}>
        <Marker position={[51.505, -0.09]} />
      </Pane>

      <Pane name="labels" style={{ zIndex: 700 }}>
        <Tooltip position={[51.505, -0.09]} permanent>
          Important Label
        </Tooltip>
      </Pane>
    </MapContainer>
  );
}

FeatureGroup with Shared Styling

function StyledFeatureGroup() {
  return (
    <FeatureGroup
      pathOptions={{
        color: "purple",
        weight: 3,
        fillColor: "lavender",
        fillOpacity: 0.5,
      }}
    >
      <Popup>These features share styling</Popup>
      <Circle center={[51.505, -0.09]} radius={200} />
      <Circle center={[51.51, -0.1]} radius={200} />
      <Rectangle bounds={[[51.49, -0.08], [51.50, -0.06]]} />
    </FeatureGroup>
  );
}

Complex Scenarios

Nested Layer Groups with Different Panes

function MultiPaneLayerGroups() {
  return (
    <MapContainer center={[51.505, -0.09]} zoom={13}>
      <TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
      
      <Pane name="background" style={{ zIndex: 400 }}>
        <LayerGroup>
          <Circle center={[51.505, -0.09]} radius={500} pathOptions={{ color: "blue" }} />
        </LayerGroup>
      </Pane>

      <Pane name="foreground" style={{ zIndex: 600 }}>
        <LayerGroup>
          <Marker position={[51.505, -0.09]} />
        </LayerGroup>
      </Pane>
    </MapContainer>
  );
}

Dynamic GeoJSON with Filters

function FilteredGeoJSON({ data, filter }) {
  const filteredData = useMemo(() => ({
    ...data,
    features: data.features.filter(feature => 
      feature.properties.category === filter
    ),
  }), [data, filter]);

  return (
    <GeoJSON
      data={filteredData}
      style={(feature) => ({
        color: feature.properties.color || "blue",
        weight: 2,
        fillOpacity: 0.5,
      })}
      onEachFeature={(feature, layer) => {
        layer.bindPopup(`<h3>${feature.properties.name}</h3>`);
      }}
    />
  );
}

Animated GeoJSON

function AnimatedGeoJSON({ data }) {
  const [visibleFeatures, setVisibleFeatures] = useState([]);

  useEffect(() => {
    let index = 0;
    const interval = setInterval(() => {
      if (index < data.features.length) {
        setVisibleFeatures(prev => [...prev, data.features[index]]);
        index++;
      } else {
        clearInterval(interval);
      }
    }, 500);

    return () => clearInterval(interval);
  }, [data]);

  const animatedData = useMemo(() => ({
    type: "FeatureCollection",
    features: visibleFeatures,
  }), [visibleFeatures]);

  return <GeoJSON data={animatedData} />;
}

Clustered GeoJSON Points

function ClusteredGeoJSON({ data, zoom }) {
  const processedData = useMemo(() => {
    if (zoom > 12) return data; // Show all points at high zoom

    // Simple clustering logic
    const clusters = new Map();
    data.features.forEach(feature => {
      if (feature.geometry.type === "Point") {
        const [lng, lat] = feature.geometry.coordinates;
        const key = `${Math.floor(lat)},${Math.floor(lng)}`;
        
        if (!clusters.has(key)) {
          clusters.set(key, {
            type: "Feature",
            geometry: { type: "Point", coordinates: [lng, lat] },
            properties: { count: 0, items: [] },
          });
        }
        
        const cluster = clusters.get(key);
        cluster.properties.count++;
        cluster.properties.items.push(feature.properties);
      }
    });

    return {
      type: "FeatureCollection",
      features: Array.from(clusters.values()),
    };
  }, [data, zoom]);

  return (
    <GeoJSON
      data={processedData}
      pointToLayer={(feature, latlng) => {
        const count = feature.properties.count;
        return L.circleMarker(latlng, {
          radius: Math.min(count * 5, 50),
          fillColor: count > 10 ? "red" : "blue",
          fillOpacity: 0.7,
        });
      }}
      onEachFeature={(feature, layer) => {
        layer.bindPopup(`${feature.properties.count} items`);
      }}
    />
  );
}

Conditional Layer Rendering

function ConditionalLayers({ showMarkers, showCircles, showPolygons }) {
  return (
    <>
      {showMarkers && (
        <LayerGroup>
          <Marker position={[51.505, -0.09]} />
          <Marker position={[51.51, -0.1]} />
        </LayerGroup>
      )}
      
      {showCircles && (
        <FeatureGroup pathOptions={{ color: "blue" }}>
          <Circle center={[51.505, -0.09]} radius={200} />
          <Circle center={[51.51, -0.1]} radius={200} />
        </FeatureGroup>
      )}
      
      {showPolygons && (
        <LayerGroup>
          <Polygon positions={[[51.515, -0.09], [51.52, -0.1], [51.52, -0.12]]} />
        </LayerGroup>
      )}
    </>
  );
}

Performance Optimization

Lazy Loading GeoJSON

function LazyGeoJSON({ url }) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetch(url)
      .then(res => res.json())
      .then(data => {
        setData(data);
        setLoading(false);
      });
  }, [url]);

  if (loading) return <div>Loading...</div>;
  if (!data) return null;

  return <GeoJSON data={data} />;
}

Memoized Layer Groups

const MemoizedLayerGroup = React.memo(({ children, pane }) => (
  <LayerGroup pane={pane}>
    {children}
  </LayerGroup>
));

function OptimizedLayers({ layers }) {
  return (
    <>
      {layers.map(layer => (
        <MemoizedLayerGroup key={layer.id} pane={layer.pane}>
          {layer.content}
        </MemoizedLayerGroup>
      ))}
    </>
  );
}

Troubleshooting

GeoJSON Not Rendering

Solutions:

  1. Verify GeoJSON data is valid
  2. Check coordinate order (GeoJSON uses [lng, lat])
  3. Ensure data prop is changing to trigger re-render
  4. Check for JavaScript errors in style/onEachFeature functions

Layer Group Not Showing

Solutions:

  1. Verify children are valid layer components
  2. Check pane settings
  3. Ensure LayerGroup is inside MapContainer
  4. Verify z-index and visibility settings

Performance Issues

Solutions:

  1. Use memoization for expensive computations
  2. Implement lazy loading for large datasets
  3. Filter features based on map bounds
  4. Simplify GeoJSON geometries
  5. Use clustering for many points

Testing Layer Groups

Testing GeoJSON Rendering

import { render } from '@testing-library/react';
import { MapContainer, GeoJSON } from 'react-leaflet';

const testGeoJSON = {
  type: "FeatureCollection",
  features: [{
    type: "Feature",
    geometry: { type: "Point", coordinates: [-0.09, 51.505] },
    properties: { name: "Test Point" }
  }]
};

test('renders GeoJSON features', () => {
  const { container } = render(
    <MapContainer center={[51.505, -0.09]} zoom={13}>
      <GeoJSON data={testGeoJSON} />
    </MapContainer>
  );
  
  expect(container).toBeInTheDocument();
});

Testing Layer Group Visibility

test('toggles layer group visibility', () => {
  const { rerender } = render(
    <MapContainer center={[51.505, -0.09]} zoom={13}>
      {true && (
        <LayerGroup>
          <Marker position={[51.505, -0.09]} />
        </LayerGroup>
      )}
    </MapContainer>
  );
  
  // Test visibility toggle
});

GeoJSON Coordinate Conversion

Important: Coordinate Order Difference

// Leaflet uses [latitude, longitude]
const leafletCoords: LatLngExpression = [51.505, -0.09];

// GeoJSON uses [longitude, latitude]
const geoJSONCoords: GeoJSONPosition = [-0.09, 51.505];

// Convert GeoJSON to Leaflet
const geoJsonData = {
  type: "Feature",
  geometry: {
    type: "Point",
    coordinates: [-0.09, 51.505] // [lng, lat] in GeoJSON
  }
};

// GeoJSON component handles conversion automatically
<GeoJSON data={geoJsonData} />

Accessibility for Layer Groups

Accessible GeoJSON Features

<GeoJSON
  data={geojsonData}
  onEachFeature={(feature, layer) => {
    if (feature.properties && feature.properties.name) {
      layer.bindPopup(
        `<div role="region" aria-label="${feature.properties.name}">
          <h3>${feature.properties.name}</h3>
          <p>${feature.properties.description}</p>
        </div>`
      );
    }
  }}
/>