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

hooks.mddocs/reference/

Custom Hooks

React Leaflet provides custom hooks for accessing the map instance and handling events from within components. These hooks must be used inside components that are children of MapContainer.

Related Documentation

  • Map Container - Context provider for hooks
  • Core APIs - useLeafletContext for advanced usage
  • Markers and Popups - Using hooks with markers
  • Controls - Creating custom controls with hooks

Capabilities

useMap Hook

Returns the Leaflet Map instance from context.

/**
 * Access the Leaflet Map instance from context
 * @returns The Leaflet Map instance
 * @throws Error if called outside MapContainer context
 */
function useMap(): Map;

Usage Example:

import { useMap } from "react-leaflet";
import { useEffect } from "react";

function MapController() {
  const map = useMap();

  useEffect(() => {
    // Fly to a new location
    map.flyTo([51.505, -0.09], 13);
  }, [map]);

  return null;
}

// Use inside MapContainer
function App() {
  return (
    <MapContainer center={[0, 0]} zoom={2}>
      <MapController />
      <TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
    </MapContainer>
  );
}

useMapEvent Hook

Attaches a single event handler to the map.

/**
 * Attach an event handler to the map
 * @param type - Leaflet event type
 * @param handler - Event handler function
 * @returns The Leaflet Map instance
 */
function useMapEvent<T extends keyof LeafletEventHandlerFnMap>(
  type: T,
  handler: LeafletEventHandlerFnMap[T]
): Map;

Usage Example:

import { useMapEvent } from "react-leaflet";
import { useState } from "react";

function ClickHandler() {
  const [position, setPosition] = useState(null);

  useMapEvent("click", (e) => {
    setPosition(e.latlng);
    console.log("Map clicked at:", e.latlng);
  });

  return position ? (
    <div>Clicked at: {position.lat.toFixed(4)}, {position.lng.toFixed(4)}</div>
  ) : null;
}

Common Event Types:

// Mouse events
useMapEvent("click", (e) => { /* ... */ });
useMapEvent("dblclick", (e) => { /* ... */ });
useMapEvent("mousedown", (e) => { /* ... */ });
useMapEvent("mouseup", (e) => { /* ... */ });
useMapEvent("mouseover", (e) => { /* ... */ });
useMapEvent("mouseout", (e) => { /* ... */ });
useMapEvent("mousemove", (e) => { /* ... */ });

// Map state events
useMapEvent("zoomend", (e) => { /* ... */ });
useMapEvent("moveend", (e) => { /* ... */ });
useMapEvent("dragend", (e) => { /* ... */ });

// Popup events
useMapEvent("popupopen", (e) => { /* ... */ });
useMapEvent("popupclose", (e) => { /* ... */ });

useMapEvents Hook

Attaches multiple event handlers to the map.

/**
 * Attach multiple event handlers to the map
 * @param handlers - Object mapping event types to handlers
 * @returns The Leaflet Map instance
 */
function useMapEvents(handlers: LeafletEventHandlerFnMap): Map;

Usage Example:

import { useMapEvents } from "react-leaflet";
import { useState } from "react";

function MapEventHandler() {
  const [mapState, setMapState] = useState({
    zoom: 13,
    center: [51.505, -0.09],
  });

  const map = useMapEvents({
    zoomend: () => {
      setMapState((prev) => ({ ...prev, zoom: map.getZoom() }));
    },
    moveend: () => {
      setMapState((prev) => ({ ...prev, center: map.getCenter() }));
    },
    click: (e) => {
      console.log("Clicked at:", e.latlng);
    },
  });

  return (
    <div>
      Zoom: {mapState.zoom}, Center: {mapState.center.lat.toFixed(4)}, {mapState.center.lng.toFixed(4)}
    </div>
  );
}

Multiple Events Example:

function LocationMarker() {
  const [position, setPosition] = useState(null);

  useMapEvents({
    click(e) {
      setPosition(e.latlng);
    },
    locationfound(e) {
      setPosition(e.latlng);
      map.flyTo(e.latlng, map.getZoom());
    },
  });

  return position === null ? null : (
    <Marker position={position}>
      <Popup>You are here</Popup>
    </Marker>
  );
}

Types

LeafletEventHandlerFnMap

// From Leaflet
interface LeafletEventHandlerFnMap {
  // Layer control events
  baselayerchange?: LayersControlEventHandlerFn;
  overlayadd?: LayersControlEventHandlerFn;
  overlayremove?: LayersControlEventHandlerFn;
  
  // Layer events
  layeradd?: LayerEventHandlerFn;
  layerremove?: LayerEventHandlerFn;
  
  // Map state events
  zoomlevelschange?: EventHandlerFn;
  unload?: EventHandlerFn;
  viewreset?: EventHandlerFn;
  load?: EventHandlerFn;
  zoomstart?: EventHandlerFn;
  movestart?: EventHandlerFn;
  zoom?: EventHandlerFn;
  move?: EventHandlerFn;
  zoomend?: EventHandlerFn;
  moveend?: EventHandlerFn;
  
  // Popup and tooltip events
  popupopen?: PopupEventHandlerFn;
  popupclose?: PopupEventHandlerFn;
  autopanstart?: EventHandlerFn;
  tooltipopen?: TooltipEventHandlerFn;
  tooltipclose?: TooltipEventHandlerFn;
  
  // Mouse events
  click?: LeafletMouseEventHandlerFn;
  dblclick?: LeafletMouseEventHandlerFn;
  mousedown?: LeafletMouseEventHandlerFn;
  mouseup?: LeafletMouseEventHandlerFn;
  mouseover?: LeafletMouseEventHandlerFn;
  mouseout?: LeafletMouseEventHandlerFn;
  mousemove?: LeafletMouseEventHandlerFn;
  contextmenu?: LeafletMouseEventHandlerFn;
  preclick?: LeafletMouseEventHandlerFn;
  
  // Keyboard events
  keypress?: LeafletKeyboardEventHandlerFn;
  keydown?: LeafletKeyboardEventHandlerFn;
  keyup?: LeafletKeyboardEventHandlerFn;
  
  // Animation and drag events
  zoomanim?: ZoomAnimEventHandlerFn;
  dragstart?: EventHandlerFn;
  drag?: EventHandlerFn;
  dragend?: DragEndEventHandlerFn;
  
  // Location events
  locationerror?: ErrorEventHandlerFn;
  locationfound?: LocationEventHandlerFn;
  
  // Resize event
  resize?: ResizeEventHandlerFn;
}

// Handler function types
type EventHandlerFn = (event: LeafletEvent) => void;
type LeafletMouseEventHandlerFn = (event: LeafletMouseEvent) => void;
type LeafletKeyboardEventHandlerFn = (event: LeafletKeyboardEvent) => void;
type PopupEventHandlerFn = (event: PopupEvent) => void;
type TooltipEventHandlerFn = (event: TooltipEvent) => void;
type LayerEventHandlerFn = (event: LayerEvent) => void;
type LayersControlEventHandlerFn = (event: LayersControlEvent) => void;
type LocationEventHandlerFn = (event: LocationEvent) => void;
type ErrorEventHandlerFn = (event: ErrorEvent) => void;
type DragEndEventHandlerFn = (event: DragEndEvent) => void;
type ZoomAnimEventHandlerFn = (event: ZoomAnimEvent) => void;
type ResizeEventHandlerFn = (event: ResizeEvent) => void;

Map of all available Leaflet event types to their handler functions.

Common Event Objects

// Mouse events
interface LeafletMouseEvent {
  latlng: LatLng;
  layerPoint: Point;
  containerPoint: Point;
  originalEvent: MouseEvent;
  type: string;
  target: any;
}

// Location events
interface LocationEvent {
  latlng: LatLng;
  bounds: LatLngBounds;
  accuracy: number;
  altitude?: number;
  altitudeAccuracy?: number;
  heading?: number;
  speed?: number;
  timestamp: number;
}

// Drag events
interface DragEndEvent {
  distance: number;
  type: string;
  target: any;
}

Important Notes

  1. Context Requirement: All hooks must be used inside components that are children of MapContainer. Using them outside will throw an error.

  2. Cleanup: Event handlers registered with hooks are automatically cleaned up when the component unmounts.

  3. Stable Handlers: For useMapEvent and useMapEvents, the handler functions are re-attached if they change. Use useCallback to prevent unnecessary re-registrations:

const handleClick = useCallback((e) => {
  console.log(e.latlng);
}, []);

useMapEvent("click", handleClick);
  1. Return Value: All hooks return the Map instance, which can be useful for accessing map methods:
const map = useMapEvents({
  click: (e) => {
    map.setView(e.latlng, map.getZoom());
  },
});
  1. Event Propagation: Events propagate from layers to the map. Use e.originalEvent.stopPropagation() to prevent this if needed.

  2. Custom Components: These hooks are essential for creating custom components that interact with the map without rendering Leaflet layers.

Examples

Click to Add Marker

function ClickToAddMarker() {
  const [markers, setMarkers] = useState([]);

  useMapEvent("click", (e) => {
    setMarkers((current) => [...current, e.latlng]);
  });

  return (
    <>
      {markers.map((position, idx) => (
        <Marker key={idx} position={position}>
          <Popup>Marker {idx + 1}</Popup>
        </Marker>
      ))}
    </>
  );
}

Display Current Zoom Level

function ZoomLevel() {
  const map = useMap();
  const [zoom, setZoom] = useState(map.getZoom());

  useMapEvent("zoomend", () => {
    setZoom(map.getZoom());
  });

  return <div className="zoom-display">Current zoom: {zoom}</div>;
}

Fit Bounds on Mount

function FitBoundsOnMount({ bounds }) {
  const map = useMap();

  useEffect(() => {
    map.fitBounds(bounds);
  }, [map, bounds]);

  return null;
}

Advanced Hook Patterns

Geolocation Hook

function useGeolocation() {
  const map = useMap();
  const [position, setPosition] = useState(null);
  const [error, setError] = useState(null);

  useEffect(() => {
    map.locate({ setView: true, maxZoom: 16 });

    const onLocationFound = (e) => {
      setPosition(e.latlng);
      setError(null);
    };

    const onLocationError = (e) => {
      setError(e.message);
    };

    map.on("locationfound", onLocationFound);
    map.on("locationerror", onLocationError);

    return () => {
      map.off("locationfound", onLocationFound);
      map.off("locationerror", onLocationError);
    };
  }, [map]);

  return { position, error };
}

// Usage
function GeolocationMarker() {
  const { position, error } = useGeolocation();

  if (error) return <div>Error: {error}</div>;
  if (!position) return null;

  return (
    <Marker position={position}>
      <Popup>You are here</Popup>
    </Marker>
  );
}

Map Bounds Hook

function useMapBounds() {
  const map = useMap();
  const [bounds, setBounds] = useState(map.getBounds());

  useMapEvents({
    moveend: () => {
      setBounds(map.getBounds());
    },
    zoomend: () => {
      setBounds(map.getBounds());
    },
  });

  return bounds;
}

// Usage
function VisibleAreaInfo() {
  const bounds = useMapBounds();
  const area = bounds.toBBoxString();

  return <div>Visible area: {area}</div>;
}

Debounced Map Events

function useDebouncedMapEvent(eventType, handler, delay = 300) {
  const map = useMap();
  const timeoutRef = useRef(null);

  useEffect(() => {
    const debouncedHandler = (e) => {
      if (timeoutRef.current) {
        clearTimeout(timeoutRef.current);
      }
      timeoutRef.current = setTimeout(() => {
        handler(e);
      }, delay);
    };

    map.on(eventType, debouncedHandler);

    return () => {
      map.off(eventType, debouncedHandler);
      if (timeoutRef.current) {
        clearTimeout(timeoutRef.current);
      }
    };
  }, [map, eventType, handler, delay]);
}

// Usage
function DebouncedSearch() {
  const handleMoveEnd = useCallback(() => {
    console.log("Map stopped moving");
    // Perform expensive operation
  }, []);

  useDebouncedMapEvent("moveend", handleMoveEnd, 500);

  return null;
}

Map State Hook

function useMapState() {
  const map = useMap();
  const [state, setState] = useState({
    center: map.getCenter(),
    zoom: map.getZoom(),
    bounds: map.getBounds(),
  });

  useMapEvents({
    moveend: () => {
      setState({
        center: map.getCenter(),
        zoom: map.getZoom(),
        bounds: map.getBounds(),
      });
    },
    zoomend: () => {
      setState({
        center: map.getCenter(),
        zoom: map.getZoom(),
        bounds: map.getBounds(),
      });
    },
  });

  return state;
}

// Usage
function MapStateDisplay() {
  const { center, zoom, bounds } = useMapState();

  return (
    <div>
      <p>Center: {center.lat.toFixed(4)}, {center.lng.toFixed(4)}</p>
      <p>Zoom: {zoom}</p>
    </div>
  );
}

Click Position Hook

function useClickPosition() {
  const [position, setPosition] = useState(null);

  useMapEvent("click", (e) => {
    setPosition(e.latlng);
  });

  return position;
}

// Usage
function ClickPositionMarker() {
  const position = useClickPosition();

  return position ? (
    <Marker position={position}>
      <Popup>
        Clicked at: {position.lat.toFixed(4)}, {position.lng.toFixed(4)}
      </Popup>
    </Marker>
  ) : null;
}

Performance Optimization

Throttled Events

function useThrottledMapEvent(eventType, handler, delay = 100) {
  const map = useMap();
  const lastCall = useRef(0);

  useEffect(() => {
    const throttledHandler = (e) => {
      const now = Date.now();
      if (now - lastCall.current >= delay) {
        lastCall.current = now;
        handler(e);
      }
    };

    map.on(eventType, throttledHandler);

    return () => {
      map.off(eventType, throttledHandler);
    };
  }, [map, eventType, handler, delay]);
}

Memoized Event Handlers

function OptimizedMapEvents() {
  const handleClick = useCallback((e) => {
    console.log("Clicked at:", e.latlng);
  }, []);

  const handleZoom = useCallback(() => {
    console.log("Zoom changed");
  }, []);

  useMapEvents({
    click: handleClick,
    zoomend: handleZoom,
  });

  return null;
}

Troubleshooting

Hook Called Outside MapContainer

Error: "No context provided: useLeafletContext() can only be used in a descendant of <MapContainer>"

Solution: Ensure component using hooks is rendered inside MapContainer:

<MapContainer center={[51.505, -0.09]} zoom={13}>
  <ComponentUsingHooks />
</MapContainer>

Events Not Firing

Solutions:

  1. Verify event type name is correct
  2. Check that map is fully initialized
  3. Ensure handler function is stable (use useCallback)
  4. Check for event propagation issues

Memory Leaks

Solutions:

  1. Always clean up event listeners in useEffect return
  2. Clear timeouts and intervals
  3. Remove references to map instance
  4. Use useCallback for stable handler references

Testing Hooks

Testing useMap Hook

import { renderHook } from '@testing-library/react';
import { useMap } from 'react-leaflet';
import { MapContainer } from 'react-leaflet';

test('useMap returns map instance', () => {
  const wrapper = ({ children }) => (
    <MapContainer center={[51.505, -0.09]} zoom={13}>
      {children}
    </MapContainer>
  );
  
  const { result } = renderHook(() => useMap(), { wrapper });
  
  expect(result.current).toBeDefined();
  expect(result.current.getZoom).toBeDefined();
});

Testing Map Event Hooks

test('useMapEvent attaches event handler', () => {
  const handleClick = jest.fn();
  
  function TestComponent() {
    useMapEvent('click', handleClick);
    return null;
  }
  
  render(
    <MapContainer center={[51.505, -0.09]} zoom={13}>
      <TestComponent />
    </MapContainer>
  );
  
  // Simulate map click and verify handler called
});

Type-Safe Hook Usage

Using Hooks with TypeScript

import { useMap, useMapEvents } from 'react-leaflet';
import type { Map, LeafletMouseEvent } from 'leaflet';

function TypeSafeMapComponent() {
  // Explicit type annotation
  const map: Map = useMap();
  
  // Type-safe event handlers
  const eventMap = useMapEvents({
    click: (e: LeafletMouseEvent) => {
      console.log('Clicked at:', e.latlng.lat, e.latlng.lng);
    },
    zoomend: () => {
      const zoom: number = map.getZoom();
      console.log('Zoom level:', zoom);
    },
  });
  
  return null;
}

Accessibility with Hooks

Focus Management

function AccessibleMapControl() {
  const map = useMap();
  
  const handleFocusMap = useCallback(() => {
    const container = map.getContainer();
    container.focus();
    container.setAttribute('aria-label', 'Map focused. Use arrow keys to pan.');
  }, [map]);
  
  return (
    <button 
      onClick={handleFocusMap}
      aria-label="Focus map for keyboard navigation"
    >
      Focus Map
    </button>
  );
}