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

map-container.mddocs/reference/

Map Container

The MapContainer component is the root component that creates and manages the Leaflet Map instance. All other react-leaflet components must be children (direct or nested) of MapContainer to access the map context.

Related Documentation

  • Hooks - Accessing map instance from child components
  • Tile Layers - Adding base maps
  • Controls - Map controls configuration
  • Core APIs - Advanced map customization

Capabilities

MapContainer Component

Creates a Leaflet map instance and provides it to child components via React context.

/**
 * Root component that creates a Leaflet map instance
 * @param props - MapContainer properties
 * @param ref - Optional ref to access the Leaflet Map instance
 */
const MapContainer: ForwardRefExoticComponent<MapContainerProps & RefAttributes<MapRef>>;

interface MapContainerProps extends MapOptions {
  /** Initial map center (latitude, longitude) */
  center?: LatLngExpression;
  /** Initial zoom level */
  zoom?: number;
  /** Fit map to these bounds instead of using center/zoom */
  bounds?: LatLngBoundsExpression;
  /** Options for fitting bounds */
  boundsOptions?: FitBoundsOptions;
  /** Child components (layers, markers, controls, etc.) */
  children?: ReactNode;
  /** CSS class for the map container div */
  className?: string;
  /** HTML id for the map container div */
  id?: string;
  /** Content displayed before map initializes */
  placeholder?: ReactNode;
  /** Inline styles for the map container div */
  style?: CSSProperties;
  /** Callback invoked when map is ready */
  whenReady?: () => void;

  // Inherited from Leaflet MapOptions:
  /** Prefer Canvas over SVG for vector layers */
  preferCanvas?: boolean;
  /** Minimum zoom level */
  minZoom?: number;
  /** Maximum zoom level */
  maxZoom?: number;
  /** Maximum bounds that user can navigate to */
  maxBounds?: LatLngBoundsExpression;
  /** Coordinate reference system */
  crs?: CRS;
  /** Whether map is draggable with mouse/touch */
  dragging?: boolean;
  /** Whether touch gestures rotate the map */
  touchZoom?: boolean;
  /** Whether scroll wheel zooms the map */
  scrollWheelZoom?: boolean;
  /** Whether double click zooms the map */
  doubleClickZoom?: boolean;
  /** Whether box zoom (shift+drag) is enabled */
  boxZoom?: boolean;
  /** Whether map automatically handles browser window resize */
  trackResize?: boolean;
  /** Whether map has zoom control */
  zoomControl?: boolean;
  /** Whether attribution control is shown */
  attributionControl?: boolean;
  /** And many more Leaflet MapOptions... */
}

type MapRef = Map | null;

Usage Example:

import { MapContainer, TileLayer } from "react-leaflet";
import { useRef } from "react";

function App() {
  const mapRef = useRef<Map>(null);

  return (
    <MapContainer
      center={[51.505, -0.09]}
      zoom={13}
      style={{ height: "100vh", width: "100%" }}
      scrollWheelZoom={false}
      ref={mapRef}
      whenReady={() => {
        console.log("Map is ready");
      }}
    >
      <TileLayer
        url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
        attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'
      />
    </MapContainer>
  );
}

Initialization Modes

MapContainer supports two initialization modes:

1. Center and Zoom:

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

2. Bounds:

<MapContainer
  bounds={[[51.49, -0.12], [51.52, -0.06]]}
  boundsOptions={{ padding: [50, 50] }}
>

3. Neither (map starts at default view):

<MapContainer zoom={2}>
  {/* Use hooks or refs to set initial view programmatically */}
</MapContainer>

Accessing the Map Instance

Using Ref:

import { useRef, useEffect } from "react";
import { MapContainer } from "react-leaflet";
import type { Map } from "leaflet";

function MyComponent() {
  const mapRef = useRef<Map>(null);

  useEffect(() => {
    if (mapRef.current) {
      // Access Leaflet Map instance
      const map = mapRef.current;
      map.setView([51.505, -0.09], 13);
    }
  }, []);

  return <MapContainer ref={mapRef} center={[51.505, -0.09]} zoom={13}>
    {/* ... */}
  </MapContainer>;
}

Using Hook (in child component):

import { useMap } from "react-leaflet";

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

  const handleClick = () => {
    map.setView([51.505, -0.09], 13);
  };

  return <button onClick={handleClick}>Reset View</button>;
}

Using whenReady Callback:

function App() {
  const handleMapReady = useCallback(() => {
    console.log("Map initialized and ready");
    // Access map via ref if needed
  }, []);

  return (
    <MapContainer
      center={[51.505, -0.09]}
      zoom={13}
      whenReady={handleMapReady}
    >
      {/* ... */}
    </MapContainer>
  );
}

Important Notes

  1. Non-Reactive Props: Most MapContainer props are only used during initialization. To update the map dynamically, use refs or hooks to access the Leaflet Map instance and call Leaflet methods directly.

  2. Required Style: The container must have explicit height and width:

    style={{ height: "400px", width: "100%" }}
  3. One Map Per Container: Each MapContainer creates exactly one Leaflet Map instance. To display multiple maps, use multiple MapContainer components.

  4. Context Provider: MapContainer provides LeafletContext to all children, enabling them to access the map instance.

  5. Placeholder: The placeholder prop displays content before the map initializes, useful for loading states:

    <MapContainer placeholder={<div>Loading map...</div>} {/* ... */}>
  6. WhenReady Callback: Use whenReady for code that should run after map initialization:

    <MapContainer whenReady={() => console.log("Map ready")} {/* ... */}>
  7. Unmounting: When MapContainer unmounts, it automatically cleans up the Leaflet map instance and removes all event listeners.

  8. Multiple Instances: You can render multiple MapContainer components on the same page, each with its own independent map instance.

  9. Conditional Rendering: If you conditionally render MapContainer, the map will be recreated each time it mounts:

    {showMap && <MapContainer center={[51.505, -0.09]} zoom={13}>...</MapContainer>}
  10. Ref Stability: The ref is populated after the component mounts. Always check if ref.current is not null before using it.

Types

MapRef

type MapRef = Map | null;

// Usage with ref
import type { Map } from 'leaflet';
const mapRef = useRef<Map | null>(null);

Ref type for accessing the Leaflet Map instance. Will be null before the map initializes.

LatLngExpression

// From Leaflet
type LatLngExpression =
  | LatLng
  | [number, number]
  | [number, number, number]
  | { lat: number; lng: number }
  | { lat: number; lng: number; alt?: number };

Flexible type for specifying latitude/longitude positions.

LatLngBoundsExpression

// From Leaflet
type LatLngBoundsExpression =
  | LatLngBounds
  | [LatLngExpression, LatLngExpression];

Type for specifying rectangular geographic bounds.

MapOptions

// From Leaflet (partial - see Leaflet docs for complete list)
interface MapOptions {
  preferCanvas?: boolean;
  attributionControl?: boolean;
  zoomControl?: boolean;
  closePopupOnClick?: boolean;
  boxZoom?: boolean;
  doubleClickZoom?: boolean | 'center';
  dragging?: boolean;
  crs?: CRS;
  center?: LatLngExpression;
  zoom?: number;
  minZoom?: number;
  maxZoom?: number;
  layers?: Layer[];
  maxBounds?: LatLngBoundsExpression;
  renderer?: Renderer;
  zoomAnimation?: boolean;
  zoomAnimationThreshold?: number;
  fadeAnimation?: boolean;
  markerZoomAnimation?: boolean;
  transform3DLimit?: number;
  inertia?: boolean;
  inertiaDeceleration?: number;
  inertiaMaxSpeed?: number;
  easeLinearity?: number;
  worldCopyJump?: boolean;
  maxBoundsViscosity?: number;
  keyboard?: boolean;
  keyboardPanDelta?: number;
  scrollWheelZoom?: boolean | 'center';
  wheelDebounceTime?: number;
  wheelPxPerZoomLevel?: number;
  tapHold?: boolean;
  tapTolerance?: number;
  touchZoom?: boolean | 'center';
  bounceAtZoomLimits?: boolean;
}

Complete MapOptions type from Leaflet with all map configuration options.

FitBoundsOptions

// From Leaflet
interface FitBoundsOptions extends ZoomPanOptions {
  /** Padding around bounds in pixels */
  paddingTopLeft?: Point;
  /** Padding around bounds in pixels */
  paddingBottomRight?: Point;
  /** Equivalent to setting both paddingTopLeft and paddingBottomRight */
  padding?: Point;
  /** Maximum zoom level to use */
  maxZoom?: number;
}

Options for fitting map to bounds.

Advanced Usage

Dynamic View Updates

Since MapContainer props are immutable, use a child component with hooks to update the view:

function ChangeView({ center, zoom }: { center: LatLngExpression; zoom: number }) {
  const map = useMap();
  
  useEffect(() => {
    map.setView(center, zoom);
  }, [map, center, zoom]);
  
  return null;
}

function App() {
  const [center, setCenter] = useState<LatLngExpression>([51.505, -0.09]);
  const [zoom, setZoom] = useState(13);

  return (
    <MapContainer center={center} zoom={zoom}>
      <ChangeView center={center} zoom={zoom} />
      <TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
    </MapContainer>
  );
}

Animated View Changes

function FlyToLocation({ center, zoom }: { center: LatLngExpression; zoom: number }) {
  const map = useMap();
  
  useEffect(() => {
    map.flyTo(center, zoom, {
      duration: 2, // Animation duration in seconds
    });
  }, [map, center, zoom]);
  
  return null;
}

Fit to Bounds Dynamically

function FitBounds({ bounds }: { bounds: LatLngBoundsExpression }) {
  const map = useMap();
  
  useEffect(() => {
    map.fitBounds(bounds, {
      padding: [50, 50],
      maxZoom: 15,
    });
  }, [map, bounds]);
  
  return null;
}

Accessing Map Events

function MapEvents() {
  const map = useMapEvents({
    zoomend: () => {
      console.log("Zoom level:", map.getZoom());
    },
    moveend: () => {
      const center = map.getCenter();
      console.log("Center:", center.lat, center.lng);
    },
  });
  
  return null;
}

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

Restricting Map Bounds

<MapContainer
  center={[51.505, -0.09]}
  zoom={13}
  maxBounds={[[51.4, -0.2], [51.6, 0.0]]}
  maxBoundsViscosity={1.0} // Prevents dragging outside bounds
  minZoom={10}
  maxZoom={18}
>
  {/* ... */}
</MapContainer>

Custom CRS (Coordinate Reference System)

import L from "leaflet";

<MapContainer
  center={[0, 0]}
  zoom={0}
  crs={L.CRS.Simple} // For non-geographic maps (e.g., game maps, floor plans)
>
  {/* ... */}
</MapContainer>

Performance Optimization

<MapContainer
  center={[51.505, -0.09]}
  zoom={13}
  preferCanvas={true} // Use Canvas renderer for better performance with many vectors
  zoomAnimation={false} // Disable zoom animation for better performance
  fadeAnimation={false} // Disable fade animation
  markerZoomAnimation={false} // Disable marker zoom animation
>
  {/* ... */}
</MapContainer>

Disabling User Interaction

<MapContainer
  center={[51.505, -0.09]}
  zoom={13}
  dragging={false}
  touchZoom={false}
  doubleClickZoom={false}
  scrollWheelZoom={false}
  boxZoom={false}
  keyboard={false}
  zoomControl={false}
>
  {/* Static map display */}
</MapContainer>

Edge Cases and Troubleshooting

Map Not Displaying

Problem: Map container is empty or shows gray area.

Solutions:

  1. Ensure Leaflet CSS is imported:

    import "leaflet/dist/leaflet.css";
  2. Set explicit height on container:

    <MapContainer style={{ height: "400px", width: "100%" }}>
  3. Verify center and zoom are provided:

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

Map Size Issues After Resize

Problem: Map doesn't resize properly when container size changes.

Solution: Call invalidateSize() after resize:

function ResizableMap() {
  const mapRef = useRef<Map>(null);

  useEffect(() => {
    const handleResize = () => {
      if (mapRef.current) {
        mapRef.current.invalidateSize();
      }
    };

    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);
  }, []);

  return <MapContainer ref={mapRef} center={[51.505, -0.09]} zoom={13}>
    {/* ... */}
  </MapContainer>;
}

Server-Side Rendering (SSR)

Problem: Leaflet doesn't work with SSR (Next.js, Gatsby, etc.).

Solution: Dynamically import MapContainer client-side only:

Next.js:

import dynamic from 'next/dynamic';

const MapContainer = dynamic(
  () => import('react-leaflet').then((mod) => mod.MapContainer),
  { ssr: false }
);

React (with lazy loading):

const MapComponent = lazy(() => import('./MapComponent'));

function App() {
  return (
    <Suspense fallback={<div>Loading map...</div>}>
      <MapComponent />
    </Suspense>
  );
}

Multiple Maps on Same Page

function MultiMapPage() {
  return (
    <div>
      <MapContainer center={[51.505, -0.09]} zoom={13} style={{ height: "400px" }}>
        <TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
      </MapContainer>
      
      <MapContainer center={[48.8566, 2.3522]} zoom={13} style={{ height: "400px" }}>
        <TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
      </MapContainer>
    </div>
  );
}

Conditional Map Rendering

Problem: Map recreates when conditionally rendered.

Solution: Use CSS to show/hide instead:

function ConditionalMap({ show }: { show: boolean }) {
  return (
    <div style={{ display: show ? 'block' : 'none' }}>
      <MapContainer center={[51.505, -0.09]} zoom={13} style={{ height: "400px" }}>
        <TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
      </MapContainer>
    </div>
  );
}

Scroll Wheel Zoom Conflicts

Problem: Scroll wheel zooms map when user tries to scroll page.

Solutions:

  1. Disable scroll wheel zoom by default:
<MapContainer scrollWheelZoom={false}>
  1. Enable only when map is focused:
function ScrollWheelZoomControl() {
  const map = useMap();

  useEffect(() => {
    const enableZoom = () => map.scrollWheelZoom.enable();
    const disableZoom = () => map.scrollWheelZoom.disable();

    map.on('focus', enableZoom);
    map.on('blur', disableZoom);

    return () => {
      map.off('focus', enableZoom);
      map.off('blur', disableZoom);
    };
  }, [map]);

  return null;
}

Memory Leaks

Problem: Memory leaks when map is unmounted and remounted frequently.

Solution: Ensure proper cleanup in child components:

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

  useEffect(() => {
    const handler = () => console.log('Map moved');
    map.on('moveend', handler);

    // IMPORTANT: Clean up event listeners
    return () => {
      map.off('moveend', handler);
    };
  }, [map]);

  return null;
}

Bounds Not Fitting Correctly

Problem: Map doesn't fit bounds as expected.

Solution: Use appropriate padding and options:

<MapContainer
  bounds={[[51.49, -0.12], [51.52, -0.06]]}
  boundsOptions={{
    padding: [50, 50], // Add padding around bounds
    maxZoom: 15, // Prevent zooming in too much
  }}
>

Touch Gestures on Mobile

Problem: Touch gestures conflict with page scrolling.

Solution: Configure touch interaction:

<MapContainer
  touchZoom={true}
  tap={true}
  tapTolerance={15} // Increase tap tolerance for better mobile UX
  dragging={true}
  scrollWheelZoom={false} // Disable on mobile
>

Best Practices

  1. Always Set Height: MapContainer requires explicit height to display properly.

  2. Use Refs for Imperative Operations: Access the map instance via refs for operations like setView, fitBounds, etc.

  3. Memoize Callbacks: Use useCallback for whenReady and other callbacks to prevent unnecessary re-renders.

  4. Lazy Load for SSR: Always use dynamic imports for server-side rendered applications.

  5. Clean Up Event Listeners: Remove event listeners in cleanup functions to prevent memory leaks.

  6. Optimize for Performance: Use preferCanvas for maps with many vector shapes.

  7. Handle Resize Events: Call invalidateSize() when container size changes.

  8. Test on Mobile: Ensure touch gestures work correctly on mobile devices.

  9. Provide Loading States: Use placeholder prop for better UX during map initialization.

  10. Document Map Configuration: Document why specific MapOptions are used for future maintainability.

Testing MapContainer

Unit Testing

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

test('renders map container', () => {
  const { container } = render(
    <MapContainer center={[51.505, -0.09]} zoom={13} style={{ height: '400px' }}>
      <TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
    </MapContainer>
  );
  
  expect(container.querySelector('.leaflet-container')).toBeInTheDocument();
});

Testing with Placeholder

test('shows placeholder before map loads', () => {
  render(
    <MapContainer 
      center={[51.505, -0.09]} 
      zoom={13}
      placeholder={<div>Loading map...</div>}
    >
      <TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
    </MapContainer>
  );
  
  // Assert placeholder is shown
});

Testing whenReady Callback

test('calls whenReady callback', () => {
  const handleReady = jest.fn();
  
  render(
    <MapContainer 
      center={[51.505, -0.09]} 
      zoom={13}
      whenReady={handleReady}
    >
      <TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
    </MapContainer>
  );
  
  // Assert callback was called
});

Accessibility for MapContainer

Keyboard Navigation

<MapContainer
  center={[51.505, -0.09]}
  zoom={13}
  keyboard={true}
  keyboardPanDelta={80}
  aria-label="Interactive map showing locations"
  tabIndex={0}
  style={{ height: '500px' }}
>
  <TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
</MapContainer>

Screen Reader Support

<div role="application" aria-label="Map application">
  <MapContainer 
    center={[51.505, -0.09]} 
    zoom={13}
    style={{ height: '500px' }}
  >
    <TileLayer 
      url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
      attribution='&copy; OpenStreetMap contributors'
    />
    <div role="status" aria-live="polite" className="sr-only">
      Map loaded. Use arrow keys to pan, plus and minus keys to zoom.
    </div>
  </MapContainer>
</div>

Type-Safe MapContainer Usage

Full TypeScript Example

import { MapContainer, TileLayer } from 'react-leaflet';
import type { Map, MapOptions, LatLngExpression } from 'leaflet';
import { useRef, useEffect, CSSProperties } from 'react';

interface MapComponentProps {
  center: LatLngExpression;
  zoom: number;
  style?: CSSProperties;
  options?: Partial<MapOptions>;
}

export function MapComponent({ center, zoom, style, options }: MapComponentProps) {
  const mapRef = useRef<Map | null>(null);

  useEffect(() => {
    if (mapRef.current) {
      // Type-safe access to map instance
      const map: Map = mapRef.current;
      console.log('Current zoom:', map.getZoom());
    }
  }, []);

  return (
    <MapContainer
      ref={mapRef}
      center={center}
      zoom={zoom}
      style={style}
      {...options}
    >
      <TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
    </MapContainer>
  );
}

## Examples

### Full-Featured Map Setup

```typescript
function FullFeaturedMap() {
  const mapRef = useRef<Map>(null);
  const [isReady, setIsReady] = useState(false);

  const handleMapReady = useCallback(() => {
    setIsReady(true);
    console.log("Map initialized");
  }, []);

  useEffect(() => {
    if (isReady && mapRef.current) {
      // Perform operations after map is ready
      mapRef.current.on('zoomend', () => {
        console.log('Zoom changed:', mapRef.current?.getZoom());
      });
    }
  }, [isReady]);

  return (
    <MapContainer
      ref={mapRef}
      center={[51.505, -0.09]}
      zoom={13}
      style={{ height: "100vh", width: "100%" }}
      scrollWheelZoom={false}
      zoomControl={false}
      attributionControl={false}
      maxBounds={[[51.4, -0.2], [51.6, 0.0]]}
      minZoom={10}
      maxZoom={18}
      whenReady={handleMapReady}
      placeholder={<div>Loading map...</div>}
    >
      <TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
      {/* Additional layers and controls */}
    </MapContainer>
  );
}

Responsive Map Container

function ResponsiveMap() {
  const containerRef = useRef<HTMLDivElement>(null);
  const mapRef = useRef<Map>(null);

  useEffect(() => {
    const resizeObserver = new ResizeObserver(() => {
      if (mapRef.current) {
        mapRef.current.invalidateSize();
      }
    });

    if (containerRef.current) {
      resizeObserver.observe(containerRef.current);
    }

    return () => {
      resizeObserver.disconnect();
    };
  }, []);

  return (
    <div ref={containerRef} style={{ width: '100%', height: '100%' }}>
      <MapContainer
        ref={mapRef}
        center={[51.505, -0.09]}
        zoom={13}
        style={{ height: "100%", width: "100%" }}
      >
        <TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
      </MapContainer>
    </div>
  );
}