tessl install tessl/npm-react-leaflet@5.0.3React components for Leaflet maps
Practical examples of common React Leaflet usage patterns in production applications.
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>
);
}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>
);
}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>
);
}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>
);
}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>
);
}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>
);
}// 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} />
))}
</>
);
}// 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>
);
}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>
);
}