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

examples

edge-cases.mdreal-world-scenarios.md
index.md
tile.json

tessl/npm-react-leaflet

tessl install tessl/npm-react-leaflet@5.0.3

React components for Leaflet maps

real-world-scenarios.mddocs/examples/

Real-World Scenarios

Practical examples of common React Leaflet usage patterns in production applications.

Table of Contents

  • Store Locator
  • Real-Time Tracking
  • Data Visualization
  • Route Planning
  • Property Listings
  • Weather Map
  • Delivery Tracking
  • Interactive Dashboard

Store Locator

Find nearest stores with search and filtering.

import { MapContainer, TileLayer, Marker, Popup, Circle } from "react-leaflet";
import { useState, useMemo } from "react";
import type { LatLngExpression } from "leaflet";

interface Store {
  id: number;
  name: string;
  position: LatLngExpression;
  address: string;
  hours: string;
  phone: string;
}

export function StoreLocator() {
  const [userLocation, setUserLocation] = useState<LatLngExpression>([51.505, -0.09]);
  const [searchRadius, setSearchRadius] = useState(5000); // meters
  const [selectedStore, setSelectedStore] = useState<number | null>(null);

  const stores: Store[] = [
    {
      id: 1,
      name: "Downtown Store",
      position: [51.505, -0.09],
      address: "123 Main St",
      hours: "9AM-9PM",
      phone: "555-0001"
    },
    {
      id: 2,
      name: "Westside Store",
      position: [51.51, -0.12],
      address: "456 West Ave",
      hours: "8AM-10PM",
      phone: "555-0002"
    },
    // ... more stores
  ];

  const nearbyStores = useMemo(() => {
    return stores.filter(store => {
      // Calculate distance (simplified)
      const [userLat, userLng] = userLocation as [number, number];
      const [storeLat, storeLng] = store.position as [number, number];
      const distance = Math.sqrt(
        Math.pow(userLat - storeLat, 2) + Math.pow(userLng - storeLng, 2)
      ) * 111000; // Rough conversion to meters
      return distance <= searchRadius;
    });
  }, [stores, userLocation, searchRadius]);

  return (
    <div style={{ display: "flex", height: "100vh" }}>
      <div style={{ width: "300px", padding: "20px", overflowY: "auto" }}>
        <h2>Store Locator</h2>
        <div>
          <label>
            Search Radius: {searchRadius}m
            <input
              type="range"
              min="1000"
              max="10000"
              step="1000"
              value={searchRadius}
              onChange={(e) => setSearchRadius(Number(e.target.value))}
            />
          </label>
        </div>
        <div style={{ marginTop: "20px" }}>
          <h3>Nearby Stores ({nearbyStores.length})</h3>
          {nearbyStores.map(store => (
            <div
              key={store.id}
              style={{
                padding: "10px",
                marginBottom: "10px",
                border: selectedStore === store.id ? "2px solid blue" : "1px solid #ccc",
                cursor: "pointer"
              }}
              onClick={() => setSelectedStore(store.id)}
            >
              <strong>{store.name}</strong>
              <p>{store.address}</p>
              <p>{store.hours}</p>
              <p>{store.phone}</p>
            </div>
          ))}
        </div>
      </div>
      
      <MapContainer
        center={userLocation}
        zoom={13}
        style={{ flex: 1 }}
      >
        <TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
        
        {/* User location */}
        <Circle
          center={userLocation}
          radius={searchRadius}
          pathOptions={{ color: "blue", fillColor: "lightblue", fillOpacity: 0.2 }}
        />
        <Marker position={userLocation}>
          <Popup>Your Location</Popup>
        </Marker>
        
        {/* Store markers */}
        {nearbyStores.map(store => (
          <Marker
            key={store.id}
            position={store.position}
            eventHandlers={{
              click: () => setSelectedStore(store.id)
            }}
          >
            <Popup>
              <div>
                <h3>{store.name}</h3>
                <p>{store.address}</p>
                <p>{store.hours}</p>
                <p>{store.phone}</p>
                <button>Get Directions</button>
              </div>
            </Popup>
          </Marker>
        ))}
      </MapContainer>
    </div>
  );
}

Real-Time Tracking

Track moving vehicles or assets in real-time.

import { MapContainer, TileLayer, Marker, Polyline, Popup } from "react-leaflet";
import { useState, useEffect, useRef } from "react";
import type { LatLngExpression } from "leaflet";

interface Vehicle {
  id: string;
  position: LatLngExpression;
  speed: number;
  heading: number;
  driver: string;
}

export function RealTimeTracking() {
  const [vehicles, setVehicles] = useState<Vehicle[]>([]);
  const [selectedVehicle, setSelectedVehicle] = useState<string | null>(null);
  const [trail, setTrail] = useState<LatLngExpression[]>([]);

  // Simulate real-time updates
  useEffect(() => {
    const interval = setInterval(() => {
      // In production, fetch from WebSocket or API
      setVehicles(prev => prev.map(vehicle => ({
        ...vehicle,
        position: [
          (vehicle.position as [number, number])[0] + (Math.random() - 0.5) * 0.001,
          (vehicle.position as [number, number])[1] + (Math.random() - 0.5) * 0.001,
        ],
        speed: Math.random() * 60,
        heading: Math.random() * 360,
      })));
    }, 2000);

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

  // Track selected vehicle trail
  useEffect(() => {
    if (selectedVehicle) {
      const vehicle = vehicles.find(v => v.id === selectedVehicle);
      if (vehicle) {
        setTrail(prev => [...prev.slice(-50), vehicle.position]);
      }
    }
  }, [vehicles, selectedVehicle]);

  return (
    <div style={{ display: "flex", height: "100vh" }}>
      <div style={{ width: "250px", padding: "20px", overflowY: "auto" }}>
        <h2>Active Vehicles</h2>
        {vehicles.map(vehicle => (
          <div
            key={vehicle.id}
            style={{
              padding: "10px",
              marginBottom: "10px",
              border: selectedVehicle === vehicle.id ? "2px solid blue" : "1px solid #ccc",
              cursor: "pointer"
            }}
            onClick={() => setSelectedVehicle(vehicle.id)}
          >
            <strong>Vehicle {vehicle.id}</strong>
            <p>Driver: {vehicle.driver}</p>
            <p>Speed: {vehicle.speed.toFixed(1)} km/h</p>
            <p>Heading: {vehicle.heading.toFixed(0)}°</p>
          </div>
        ))}
      </div>
      
      <MapContainer center={[51.505, -0.09]} zoom={13} style={{ flex: 1 }}>
        <TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
        
        {/* Vehicle markers */}
        {vehicles.map(vehicle => (
          <Marker
            key={vehicle.id}
            position={vehicle.position}
            eventHandlers={{
              click: () => setSelectedVehicle(vehicle.id)
            }}
          >
            <Popup>
              <div>
                <h3>Vehicle {vehicle.id}</h3>
                <p>Driver: {vehicle.driver}</p>
                <p>Speed: {vehicle.speed.toFixed(1)} km/h</p>
                <p>Heading: {vehicle.heading.toFixed(0)}°</p>
              </div>
            </Popup>
          </Marker>
        ))}
        
        {/* Trail for selected vehicle */}
        {selectedVehicle && trail.length > 1 && (
          <Polyline
            positions={trail}
            pathOptions={{ color: "blue", weight: 3, opacity: 0.7 }}
          />
        )}
      </MapContainer>
    </div>
  );
}

Data Visualization

Visualize geographic data with heatmaps and clusters.

import { MapContainer, TileLayer, Circle, Popup } from "react-leaflet";
import { useMemo } from "react";

interface DataPoint {
  id: number;
  position: [number, number];
  value: number;
  label: string;
}

export function DataVisualization() {
  const data: DataPoint[] = [
    { id: 1, position: [51.505, -0.09], value: 85, label: "Zone A" },
    { id: 2, position: [51.51, -0.1], value: 62, label: "Zone B" },
    { id: 3, position: [51.49, -0.08], value: 93, label: "Zone C" },
    // ... more data points
  ];

  const getColor = (value: number) => {
    if (value > 80) return "#d73027";
    if (value > 60) return "#fc8d59";
    if (value > 40) return "#fee08b";
    if (value > 20) return "#d9ef8b";
    return "#91cf60";
  };

  const getRadius = (value: number) => {
    return value * 5; // Scale radius based on value
  };

  return (
    <div>
      <div style={{ padding: "20px", background: "#f5f5f5" }}>
        <h2>Data Visualization</h2>
        <div style={{ display: "flex", gap: "20px", alignItems: "center" }}>
          <span>Legend:</span>
          <div style={{ display: "flex", gap: "10px" }}>
            <div><span style={{ background: "#d73027", padding: "5px 10px", color: "white" }}>80-100</span></div>
            <div><span style={{ background: "#fc8d59", padding: "5px 10px", color: "white" }}>60-80</span></div>
            <div><span style={{ background: "#fee08b", padding: "5px 10px" }}>40-60</span></div>
            <div><span style={{ background: "#d9ef8b", padding: "5px 10px" }}>20-40</span></div>
            <div><span style={{ background: "#91cf60", padding: "5px 10px" }}>0-20</span></div>
          </div>
        </div>
      </div>
      
      <MapContainer center={[51.505, -0.09]} zoom={12} style={{ height: "600px" }}>
        <TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
        
        {data.map(point => (
          <Circle
            key={point.id}
            center={point.position}
            radius={getRadius(point.value)}
            pathOptions={{
              color: getColor(point.value),
              fillColor: getColor(point.value),
              fillOpacity: 0.6,
              weight: 2
            }}
          >
            <Popup>
              <div>
                <h3>{point.label}</h3>
                <p>Value: {point.value}</p>
              </div>
            </Popup>
          </Circle>
        ))}
      </MapContainer>
    </div>
  );
}

Route Planning

Plan and display routes with waypoints.

import { MapContainer, TileLayer, Marker, Polyline, Popup } from "react-leaflet";
import { useState } from "react";
import type { LatLngExpression } from "leaflet";

interface Waypoint {
  id: number;
  position: LatLngExpression;
  name: string;
  arrivalTime?: string;
}

export function RoutePlanning() {
  const [waypoints, setWaypoints] = useState<Waypoint[]>([
    { id: 1, position: [51.505, -0.09], name: "Start", arrivalTime: "9:00 AM" },
    { id: 2, position: [51.51, -0.1], name: "Stop 1", arrivalTime: "9:30 AM" },
    { id: 3, position: [51.52, -0.08], name: "Stop 2", arrivalTime: "10:00 AM" },
    { id: 4, position: [51.515, -0.12], name: "End", arrivalTime: "10:30 AM" },
  ]);

  const routePath = waypoints.map(wp => wp.position);

  const addWaypoint = (position: LatLngExpression) => {
    const newWaypoint: Waypoint = {
      id: Date.now(),
      position,
      name: `Stop ${waypoints.length}`,
    };
    setWaypoints([...waypoints, newWaypoint]);
  };

  const removeWaypoint = (id: number) => {
    setWaypoints(waypoints.filter(wp => wp.id !== id));
  };

  return (
    <div style={{ display: "flex", height: "100vh" }}>
      <div style={{ width: "300px", padding: "20px", overflowY: "auto" }}>
        <h2>Route Plan</h2>
        <p>Total waypoints: {waypoints.length}</p>
        <div>
          {waypoints.map((wp, index) => (
            <div
              key={wp.id}
              style={{
                padding: "10px",
                marginBottom: "10px",
                border: "1px solid #ccc",
                display: "flex",
                justifyContent: "space-between",
                alignItems: "center"
              }}
            >
              <div>
                <strong>{index + 1}. {wp.name}</strong>
                {wp.arrivalTime && <p>ETA: {wp.arrivalTime}</p>}
              </div>
              <button onClick={() => removeWaypoint(wp.id)}>Remove</button>
            </div>
          ))}
        </div>
      </div>
      
      <MapContainer center={[51.51, -0.09]} zoom={13} style={{ flex: 1 }}>
        <TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
        
        {/* Route line */}
        <Polyline
          positions={routePath}
          pathOptions={{ color: "blue", weight: 4, opacity: 0.7 }}
        />
        
        {/* Waypoint markers */}
        {waypoints.map((wp, index) => (
          <Marker key={wp.id} position={wp.position}>
            <Popup>
              <div>
                <h3>{index + 1}. {wp.name}</h3>
                {wp.arrivalTime && <p>ETA: {wp.arrivalTime}</p>}
                <button onClick={() => removeWaypoint(wp.id)}>Remove</button>
              </div>
            </Popup>
          </Marker>
        ))}
      </MapContainer>
    </div>
  );
}

Property Listings

Display property listings with filtering.

import { MapContainer, TileLayer, Marker, Popup } from "react-leaflet";
import { useState, useMemo } from "react";
import type { LatLngExpression } from "leaflet";

interface Property {
  id: number;
  position: LatLngExpression;
  title: string;
  price: number;
  bedrooms: number;
  bathrooms: number;
  sqft: number;
  image: string;
  type: "house" | "apartment" | "condo";
}

export function PropertyListings() {
  const [priceRange, setPriceRange] = useState([0, 1000000]);
  const [minBedrooms, setMinBedrooms] = useState(0);
  const [propertyType, setPropertyType] = useState<string>("all");

  const properties: Property[] = [
    {
      id: 1,
      position: [51.505, -0.09],
      title: "Modern Apartment",
      price: 450000,
      bedrooms: 2,
      bathrooms: 2,
      sqft: 1200,
      image: "/property1.jpg",
      type: "apartment"
    },
    // ... more properties
  ];

  const filteredProperties = useMemo(() => {
    return properties.filter(prop => {
      return (
        prop.price >= priceRange[0] &&
        prop.price <= priceRange[1] &&
        prop.bedrooms >= minBedrooms &&
        (propertyType === "all" || prop.type === propertyType)
      );
    });
  }, [properties, priceRange, minBedrooms, propertyType]);

  return (
    <div style={{ display: "flex", height: "100vh" }}>
      <div style={{ width: "350px", padding: "20px", overflowY: "auto" }}>
        <h2>Property Search</h2>
        
        <div style={{ marginBottom: "20px" }}>
          <label>
            Price Range: ${priceRange[0].toLocaleString()} - ${priceRange[1].toLocaleString()}
            <input
              type="range"
              min="0"
              max="1000000"
              step="50000"
              value={priceRange[1]}
              onChange={(e) => setPriceRange([0, Number(e.target.value)])}
            />
          </label>
        </div>
        
        <div style={{ marginBottom: "20px" }}>
          <label>
            Min Bedrooms:
            <select value={minBedrooms} onChange={(e) => setMinBedrooms(Number(e.target.value))}>
              <option value="0">Any</option>
              <option value="1">1+</option>
              <option value="2">2+</option>
              <option value="3">3+</option>
              <option value="4">4+</option>
            </select>
          </label>
        </div>
        
        <div style={{ marginBottom: "20px" }}>
          <label>
            Property Type:
            <select value={propertyType} onChange={(e) => setPropertyType(e.target.value)}>
              <option value="all">All</option>
              <option value="house">House</option>
              <option value="apartment">Apartment</option>
              <option value="condo">Condo</option>
            </select>
          </label>
        </div>
        
        <h3>Results ({filteredProperties.length})</h3>
        {filteredProperties.map(prop => (
          <div key={prop.id} style={{ marginBottom: "15px", border: "1px solid #ccc", padding: "10px" }}>
            <h4>{prop.title}</h4>
            <p>${prop.price.toLocaleString()}</p>
            <p>{prop.bedrooms} bed | {prop.bathrooms} bath | {prop.sqft} sqft</p>
          </div>
        ))}
      </div>
      
      <MapContainer center={[51.505, -0.09]} zoom={13} style={{ flex: 1 }}>
        <TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
        
        {filteredProperties.map(prop => (
          <Marker key={prop.id} position={prop.position}>
            <Popup>
              <div style={{ width: "200px" }}>
                <h3>{prop.title}</h3>
                <p style={{ fontSize: "18px", fontWeight: "bold" }}>
                  ${prop.price.toLocaleString()}
                </p>
                <p>{prop.bedrooms} bed | {prop.bathrooms} bath</p>
                <p>{prop.sqft} sqft</p>
                <button>View Details</button>
              </div>
            </Popup>
          </Marker>
        ))}
      </MapContainer>
    </div>
  );
}

Weather Map

Display weather data with overlays.

import { MapContainer, TileLayer, Circle, Popup } from "react-leaflet";
import { useState, useEffect } from "react";

interface WeatherData {
  id: number;
  position: [number, number];
  city: string;
  temperature: number;
  condition: string;
  humidity: number;
  windSpeed: number;
}

export function WeatherMap() {
  const [weatherData, setWeatherData] = useState<WeatherData[]>([]);
  const [showTemperature, setShowTemperature] = useState(true);

  useEffect(() => {
    // In production, fetch from weather API
    setWeatherData([
      {
        id: 1,
        position: [51.505, -0.09],
        city: "London",
        temperature: 18,
        condition: "Cloudy",
        humidity: 65,
        windSpeed: 15
      },
      // ... more weather data
    ]);
  }, []);

  const getTemperatureColor = (temp: number) => {
    if (temp > 25) return "#d73027";
    if (temp > 15) return "#fee08b";
    return "#91bfdb";
  };

  return (
    <div>
      <div style={{ padding: "20px", background: "#f5f5f5" }}>
        <h2>Weather Map</h2>
        <label>
          <input
            type="checkbox"
            checked={showTemperature}
            onChange={(e) => setShowTemperature(e.target.checked)}
          />
          Show Temperature Overlay
        </label>
      </div>
      
      <MapContainer center={[51.505, -0.09]} zoom={6} style={{ height: "600px" }}>
        <TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
        
        {weatherData.map(data => (
          <Circle
            key={data.id}
            center={data.position}
            radius={showTemperature ? 50000 : 20000}
            pathOptions={{
              color: getTemperatureColor(data.temperature),
              fillColor: getTemperatureColor(data.temperature),
              fillOpacity: showTemperature ? 0.4 : 0.2
            }}
          >
            <Popup>
              <div>
                <h3>{data.city}</h3>
                <p>Temperature: {data.temperature}°C</p>
                <p>Condition: {data.condition}</p>
                <p>Humidity: {data.humidity}%</p>
                <p>Wind: {data.windSpeed} km/h</p>
              </div>
            </Popup>
          </Circle>
        ))}
      </MapContainer>
    </div>
  );
}

Best Practices

Performance Optimization

// Memoize marker components
const MemoizedMarker = React.memo(({ position, title }) => (
  <Marker position={position}>
    <Popup>{title}</Popup>
  </Marker>
));

// Virtualize large lists
function VirtualizedMarkers({ markers }) {
  const map = useMap();
  const [visibleMarkers, setVisibleMarkers] = useState([]);

  useEffect(() => {
    const updateVisible = () => {
      const bounds = map.getBounds();
      const visible = markers.filter(m => bounds.contains(m.position));
      setVisibleMarkers(visible);
    };

    updateVisible();
    map.on('moveend', updateVisible);
    return () => map.off('moveend', updateVisible);
  }, [map, markers]);

  return (
    <>
      {visibleMarkers.map(marker => (
        <MemoizedMarker key={marker.id} {...marker} />
      ))}
    </>
  );
}

State Management

// Use context for shared map state
const MapContext = createContext(null);

function MapProvider({ children }) {
  const [selectedFeature, setSelectedFeature] = useState(null);
  const [filters, setFilters] = useState({});
  
  return (
    <MapContext.Provider value={{ selectedFeature, setSelectedFeature, filters, setFilters }}>
      {children}
    </MapContext.Provider>
  );
}

Error Handling

function MapWithErrorBoundary() {
  return (
    <ErrorBoundary fallback={<div>Map failed to load</div>}>
      <MapContainer center={[51.505, -0.09]} zoom={13}>
        <TileLayer 
          url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
          eventHandlers={{
            tileerror: (error) => console.error('Tile load error:', error)
          }}
        />
      </MapContainer>
    </ErrorBoundary>
  );
}

Additional Resources

  • Edge Cases & Advanced Scenarios
  • API Reference
  • Quick Start Guide