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

markers-popups.mddocs/reference/

Markers and Popups

Components for placing markers on the map with optional popups and tooltips for displaying information.

Related Documentation

  • Map Container - Core map component
  • Vector Shapes - Alternative visualization methods
  • Layer Groups - Organizing multiple markers
  • Hooks - Programmatic marker control

Capabilities

Marker Component

Places an icon marker at a specific geographic location.

/**
 * Component for displaying an icon marker on the map
 * @param props - Marker properties
 */
const Marker: FunctionComponent<MarkerProps>;

interface MarkerProps extends MarkerOptions, EventedProps {
  /** Geographic position of the marker (required) */
  position: LatLngExpression;
  /** Child components (Popup, Tooltip) */
  children?: ReactNode;
}

Usage Example:

import { MapContainer, TileLayer, Marker, Popup } 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" />
      <Marker position={[51.505, -0.09]}>
        <Popup>
          A marker with a popup.
        </Popup>
      </Marker>
    </MapContainer>
  );
}

Multiple Markers:

const locations = [
  { id: 1, position: [51.505, -0.09], title: "Location 1" },
  { id: 2, position: [51.51, -0.1], title: "Location 2" },
  { id: 3, position: [51.49, -0.08], title: "Location 3" },
];

function Map() {
  return (
    <MapContainer center={[51.505, -0.09]} zoom={13}>
      <TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
      {locations.map((loc) => (
        <Marker key={loc.id} position={loc.position}>
          <Popup>{loc.title}</Popup>
        </Marker>
      ))}
    </MapContainer>
  );
}

Popup Component

Information popup that can be attached to markers or displayed standalone.

/**
 * Component for displaying popup overlays
 * @param props - Popup properties
 */
const Popup: FunctionComponent<PopupProps>;

interface PopupProps extends PopupOptions, EventedProps {
  /** Popup content */
  children?: ReactNode;
  /** Position for standalone popup (not attached to marker) */
  position?: LatLngExpression;
}

Popup Attached to Marker:

<Marker position={[51.505, -0.09]}>
  <Popup>
    <h3>Marker Title</h3>
    <p>Marker description</p>
  </Popup>
</Marker>

Standalone Popup:

<Popup position={[51.505, -0.09]}>
  <div>
    <h3>Standalone Popup</h3>
    <p>This popup is not attached to a marker</p>
  </div>
</Popup>

Tooltip Component

Small informational tooltip that appears on hover or can be permanent.

/**
 * Component for displaying tooltip overlays
 * @param props - Tooltip properties
 */
const Tooltip: FunctionComponent<TooltipProps>;

interface TooltipProps extends TooltipOptions, EventedProps {
  /** Tooltip content */
  children?: ReactNode;
  /** Position for standalone tooltip */
  position?: LatLngExpression;
}

Tooltip on Marker:

<Marker position={[51.505, -0.09]}>
  <Tooltip>Hover tooltip text</Tooltip>
</Marker>

Permanent Tooltip:

<Marker position={[51.505, -0.09]}>
  <Tooltip permanent>Always visible label</Tooltip>
</Marker>

Tooltip with Popup:

<Marker position={[51.505, -0.09]}>
  <Popup>Detailed information</Popup>
  <Tooltip>Quick label</Tooltip>
</Marker>

Types

MarkerOptions

// From Leaflet
interface MarkerOptions extends InteractiveLayerOptions {
  /** Icon instance to use for the marker */
  icon?: Icon | DivIcon;
  /** Whether the marker is keyboard accessible */
  keyboard?: boolean;
  /** Title attribute for the marker */
  title?: string;
  /** Alt text for the marker image */
  alt?: string;
  /** z-index offset for the marker */
  zIndexOffset?: number;
  /** Marker opacity (0.0 - 1.0) */
  opacity?: number;
  /** Whether the marker can be moved by dragging */
  draggable?: boolean;
  /** Whether to show the marker on top while dragging */
  autoPan?: boolean;
  /** Options for auto panning */
  autoPanPadding?: Point;
  /** Speed of auto panning in pixels */
  autoPanSpeed?: number;
  /** Whether to bring marker to front on mouse over */
  riseOnHover?: boolean;
  /** z-index offset when hovered */
  riseOffset?: number;
  /** Pane where the marker will be added */
  pane?: string;
  /** Attribution text */
  attribution?: string;
}

interface InteractiveLayerOptions extends LayerOptions {
  /** Whether layer responds to mouse events */
  interactive?: boolean;
  /** Whether mouse events bubble to map */
  bubblingMouseEvents?: boolean;
}

PopupOptions

// From Leaflet
interface PopupOptions extends DivOverlayOptions {
  /** Maximum width of the popup in pixels */
  maxWidth?: number;
  /** Minimum width of the popup in pixels */
  minWidth?: number;
  /** Maximum height; sets scrollable container if exceeded */
  maxHeight?: number;
  /** Whether to auto pan on popup open */
  autoPan?: boolean;
  /** Margin between popup and map edge */
  autoPanPaddingTopLeft?: Point;
  /** Margin between popup and map edge */
  autoPanPaddingBottomRight?: Point;
  /** Padding of auto pan */
  autoPanPadding?: Point;
  /** Keep popup in view during pan/zoom */
  keepInView?: boolean;
  /** Whether to show close button */
  closeButton?: boolean;
  /** Whether to automatically close previous popup */
  autoClose?: boolean;
  /** Whether to close on Escape key */
  closeOnEscapeKey?: boolean;
  /** Whether to close when clicking the map */
  closeOnClick?: boolean;
  /** CSS class name for the popup */
  className?: string;
}

interface DivOverlayOptions {
  /** Offset of the overlay position */
  offset?: Point;
  /** CSS class name */
  className?: string;
  /** Map pane where the overlay will be added */
  pane?: string;
}

type Point = [number, number] | { x: number; y: number };

TooltipOptions

// From Leaflet
interface TooltipOptions extends DivOverlayOptions {
  /** Direction where to open the tooltip relative to source */
  direction?: 'right' | 'left' | 'top' | 'bottom' | 'center' | 'auto';
  /** Whether tooltip is permanent (always visible) */
  permanent?: boolean;
  /** Whether tooltip stays open on mouse out */
  sticky?: boolean;
  /** Opacity of the tooltip */
  opacity?: number;
  /** Offset of tooltip position */
  offset?: Point;
  /** CSS class name for the tooltip */
  className?: string;
}

Custom Icons

Using Custom Icon

import { Icon } from "leaflet";

const customIcon = new Icon({
  iconUrl: "/marker-icon.png",
  iconSize: [25, 41],
  iconAnchor: [12, 41],
  popupAnchor: [1, -34],
  shadowUrl: "/marker-shadow.png",
  shadowSize: [41, 41],
});

<Marker position={[51.505, -0.09]} icon={customIcon}>
  <Popup>Marker with custom icon</Popup>
</Marker>

Using DivIcon

import { DivIcon } from "leaflet";

const divIcon = new DivIcon({
  html: '<div class="custom-marker">📍</div>',
  className: "custom-div-icon",
  iconSize: [30, 30],
  iconAnchor: [15, 30],
});

<Marker position={[51.505, -0.09]} icon={divIcon} />

SVG Icon

const svgIcon = new DivIcon({
  html: `
    <svg width="30" height="40" viewBox="0 0 30 40">
      <path d="M15,0 C6.7,0 0,6.7 0,15 C0,26.3 15,40 15,40 S30,26.3 30,15 C30,6.7 23.3,0 15,0 Z" 
            fill="#ff0000" stroke="#fff" stroke-width="2"/>
      <circle cx="15" cy="15" r="5" fill="#fff"/>
    </svg>
  `,
  className: "",
  iconSize: [30, 40],
  iconAnchor: [15, 40],
});

<Marker position={[51.505, -0.09]} icon={svgIcon} />

Important Notes

  1. Position Updates: The marker position can be updated dynamically by changing the position prop:

    <Marker position={currentPosition} />
  2. Draggable Markers: Enable marker dragging with event handler:

    <Marker
      position={position}
      draggable={true}
      eventHandlers={{
        dragend: (e) => {
          const newPos = e.target.getLatLng();
          console.log("New position:", newPos);
        },
      }}
    />
  3. Popup Control: Popups automatically open when marker is clicked by default. Control this behavior:

    <Popup autoClose={false} closeOnClick={false}>
      Always open popup
    </Popup>
  4. Tooltip Directions: Tooltips can open in different directions:

    <Tooltip direction="top">Appears above marker</Tooltip>
    <Tooltip direction="right">Appears to the right</Tooltip>
  5. Event Handling: Attach event handlers to markers:

    <Marker
      position={[51.505, -0.09]}
      eventHandlers={{
        click: (e) => console.log("Marker clicked", e),
        mouseover: (e) => console.log("Mouse over marker"),
        dragstart: (e) => console.log("Drag started"),
        dragend: (e) => console.log("Drag ended", e.target.getLatLng()),
      }}
    />
  6. Z-Index: Control marker stacking with zIndexOffset:

    <Marker position={pos1} zIndexOffset={1000} />
    <Marker position={pos2} zIndexOffset={2000} />
  7. Opacity: Adjust marker transparency:

    <Marker position={[51.505, -0.09]} opacity={0.5} />
  8. Keyboard Access: Markers are keyboard accessible by default:

    <Marker position={[51.505, -0.09]} keyboard={true} />
  9. Default Icon Fix: In bundled applications, fix default icon paths:

    import L from "leaflet";
    import icon from "leaflet/dist/images/marker-icon.png";
    import iconShadow from "leaflet/dist/images/marker-shadow.png";
    
    let DefaultIcon = L.icon({
      iconUrl: icon,
      shadowUrl: iconShadow,
      iconSize: [25, 41],
      iconAnchor: [12, 41],
    });
    L.Marker.prototype.options.icon = DefaultIcon;
  10. Popup Content: Popups can contain any React content including interactive elements:

    <Popup>
      <div>
        <h3>Interactive Popup</h3>
        <button onClick={() => alert('Clicked!')}>Click me</button>
      </div>
    </Popup>

Examples

Interactive Marker with State

function InteractiveMarker() {
  const [position, setPosition] = useState([51.505, -0.09]);

  return (
    <Marker
      position={position}
      draggable={true}
      eventHandlers={{
        dragend: (e) => {
          const newPos = e.target.getLatLng();
          setPosition([newPos.lat, newPos.lng]);
        },
      }}
    >
      <Popup>
        Position: {position[0].toFixed(4)}, {position[1].toFixed(4)}
      </Popup>
    </Marker>
  );
}

Marker with Custom Icon and Permanent Tooltip

import { Icon } from "leaflet";

const icon = new Icon({
  iconUrl: "/custom-marker.png",
  iconSize: [32, 32],
  iconAnchor: [16, 32],
});

<Marker position={[51.505, -0.09]} icon={icon}>
  <Tooltip permanent direction="top">
    Important Location
  </Tooltip>
  <Popup>
    <h3>Important Location</h3>
    <p>More details here</p>
  </Popup>
</Marker>

Marker Cluster Simulation

function MarkerList({ locations }) {
  return (
    <>
      {locations.map((loc, idx) => (
        <Marker
          key={loc.id}
          position={loc.position}
          eventHandlers={{
            click: () => console.log(`Clicked marker ${loc.id}`),
          }}
        >
          <Popup>
            <div>
              <h4>{loc.title}</h4>
              <p>{loc.description}</p>
            </div>
          </Popup>
          <Tooltip>{loc.title}</Tooltip>
        </Marker>
      ))}
    </>
  );
}

Popup with React Components

function MarkerWithRichPopup() {
  const [likes, setLikes] = useState(0);

  return (
    <Marker position={[51.505, -0.09]}>
      <Popup>
        <div>
          <h3>Interactive Popup</h3>
          <p>Likes: {likes}</p>
          <button onClick={() => setLikes(likes + 1)}>
            Like
          </button>
        </div>
      </Popup>
    </Marker>
  );
}

Animated Marker

function AnimatedMarker({ position }) {
  const markerRef = useRef(null);

  useEffect(() => {
    if (markerRef.current) {
      const marker = markerRef.current;
      // Animate marker with CSS or Leaflet animations
      marker.setOpacity(0);
      setTimeout(() => marker.setOpacity(1), 100);
    }
  }, [position]);

  return (
    <Marker ref={markerRef} position={position}>
      <Popup>Animated marker</Popup>
    </Marker>
  );
}

Conditional Marker Rendering

function ConditionalMarkers({ markers, filter }) {
  const filteredMarkers = markers.filter(m => m.category === filter);

  return (
    <>
      {filteredMarkers.map(marker => (
        <Marker key={marker.id} position={marker.position}>
          <Popup>{marker.name}</Popup>
        </Marker>
      ))}
    </>
  );
}

Marker with Custom Popup Styling

<Marker position={[51.505, -0.09]}>
  <Popup
    className="custom-popup"
    maxWidth={300}
    minWidth={200}
    closeButton={false}
    autoPan={true}
  >
    <div style={{ padding: '10px', backgroundColor: '#f0f0f0' }}>
      <h3 style={{ margin: 0 }}>Custom Styled Popup</h3>
      <p>With custom styling</p>
    </div>
  </Popup>
</Marker>

Advanced Patterns

Dynamic Icon Based on State

function DynamicIconMarker({ position, status }) {
  const icon = useMemo(() => {
    const color = status === 'active' ? 'green' : 'red';
    return new DivIcon({
      html: `<div style="background-color: ${color}; width: 20px; height: 20px; border-radius: 50%;"></div>`,
      className: '',
      iconSize: [20, 20],
    });
  }, [status]);

  return (
    <Marker position={position} icon={icon}>
      <Popup>Status: {status}</Popup>
    </Marker>
  );
}

Marker with Loading State

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

  useEffect(() => {
    fetch(`/api/markers/${id}`)
      .then(res => res.json())
      .then(data => {
        setData(data);
        setLoading(false);
      });
  }, [id]);

  if (loading || !data) return null;

  return (
    <Marker position={data.position}>
      <Popup>
        <h3>{data.title}</h3>
        <p>{data.description}</p>
      </Popup>
    </Marker>
  );
}

Marker Clustering (Manual Implementation)

function ClusteredMarkers({ markers, zoom }) {
  const clustered = useMemo(() => {
    if (zoom > 12) return markers; // Show all markers at high zoom
    
    // Simple clustering logic
    const clusters = new Map();
    markers.forEach(marker => {
      const key = `${Math.floor(marker.position[0])},${Math.floor(marker.position[1])}`;
      if (!clusters.has(key)) {
        clusters.set(key, []);
      }
      clusters.get(key).push(marker);
    });
    
    return Array.from(clusters.values()).map(group => ({
      position: group[0].position,
      count: group.length,
      markers: group,
    }));
  }, [markers, zoom]);

  return (
    <>
      {clustered.map((cluster, idx) => (
        <Marker key={idx} position={cluster.position}>
          <Popup>
            {cluster.count} markers
            {cluster.markers.map(m => (
              <div key={m.id}>{m.title}</div>
            ))}
          </Popup>
        </Marker>
      ))}
    </>
  );
}

Marker with Ref Access

function MarkerWithRef() {
  const markerRef = useRef(null);

  const handleClick = () => {
    if (markerRef.current) {
      markerRef.current.openPopup();
    }
  };

  return (
    <>
      <button onClick={handleClick}>Open Marker Popup</button>
      <Marker ref={markerRef} position={[51.505, -0.09]}>
        <Popup>Controlled popup</Popup>
      </Marker>
    </>
  );
}

Performance Optimization

Memoized Markers

const MarkerComponent = React.memo(({ position, title }) => (
  <Marker position={position}>
    <Popup>{title}</Popup>
  </Marker>
));

function OptimizedMarkerList({ markers }) {
  return (
    <>
      {markers.map(marker => (
        <MarkerComponent
          key={marker.id}
          position={marker.position}
          title={marker.title}
        />
      ))}
    </>
  );
}

Virtualized Markers (Only Render Visible)

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

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

    updateVisibleMarkers();
    map.on('moveend', updateVisibleMarkers);
    map.on('zoomend', updateVisibleMarkers);

    return () => {
      map.off('moveend', updateVisibleMarkers);
      map.off('zoomend', updateVisibleMarkers);
    };
  }, [map, markers]);

  return (
    <>
      {visibleMarkers.map(marker => (
        <Marker key={marker.id} position={marker.position}>
          <Popup>{marker.title}</Popup>
        </Marker>
      ))}
    </>
  );
}

Troubleshooting

Markers Not Appearing

Solutions:

  1. Fix default icon paths (see note #9)
  2. Verify position coordinates are valid
  3. Check z-index and pane settings
  4. Ensure markers are inside MapContainer

Popup Not Opening

Solutions:

  1. Verify Popup is child of Marker
  2. Check autoClose and closeOnClick settings
  3. Ensure marker is interactive
  4. Check for JavaScript errors in popup content

Draggable Marker Not Working

Solutions:

  1. Set draggable={true} on Marker
  2. Attach dragend event handler
  3. Ensure map dragging doesn't interfere
  4. Check for CSS conflicts

Memory Leaks with Many Markers

Solutions:

  1. Use React.memo for marker components
  2. Implement virtualization
  3. Remove event listeners in cleanup
  4. Consider marker clustering libraries

Testing Markers

Unit Testing

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

test('renders marker with popup', () => {
  render(
    <MapContainer center={[51.505, -0.09]} zoom={13}>
      <Marker position={[51.505, -0.09]}>
        <Popup>Test Popup</Popup>
      </Marker>
    </MapContainer>
  );
  
  // Add assertions based on your testing needs
});

Testing Marker Interactions

test('handles marker click events', () => {
  const handleClick = jest.fn();
  
  render(
    <MapContainer center={[51.505, -0.09]} zoom={13}>
      <Marker 
        position={[51.505, -0.09]}
        eventHandlers={{ click: handleClick }}
      />
    </MapContainer>
  );
  
  // Simulate click and verify handler called
});

Accessibility Considerations

Keyboard Accessible Markers

<Marker 
  position={[51.505, -0.09]}
  keyboard={true}
  title="Location marker - Press Enter to open details"
>
  <Popup>
    <div role="dialog" aria-label="Location details">
      <h3>Location Name</h3>
      <p>Additional information</p>
    </div>
  </Popup>
</Marker>

Screen Reader Support

<Marker position={[51.505, -0.09]}>
  <Popup>
    <div aria-live="polite">
      <span className="sr-only">Marker at coordinates {lat}, {lng}</span>
      <h3>{locationName}</h3>
    </div>
  </Popup>
</Marker>

Performance Best Practices

  1. Memoize Markers: Use React.memo for marker components
  2. Virtualize Large Lists: Only render visible markers
  3. Cluster Markers: Use clustering for many markers
  4. Debounce Updates: Debounce position updates
  5. Lazy Load Icons: Load custom icons lazily
  6. Optimize Popups: Render popup content on demand
  7. Clean Up: Remove event listeners in cleanup functions