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

core-apis.mddocs/reference/

Core APIs (@react-leaflet/core)

Advanced APIs from the @react-leaflet/core package for creating custom components, extending react-leaflet functionality, and building wrapper libraries. These APIs provide the foundation for all react-leaflet components and enable developers to create their own Leaflet-React integrations.

Related Documentation

  • Hooks - useLeafletContext hook usage
  • Map Container - Understanding the context provider
  • Controls - Creating custom controls
  • Markers and Popups - Extending marker functionality

Prerequisites

Before using Core APIs, you should be familiar with:

  • React hooks and component lifecycle
  • Leaflet API and class structure
  • TypeScript generics (for type-safe components)
  • React context and refs

Package Information

The @react-leaflet/core package is a peer dependency of react-leaflet that exposes the core integration layer between React and Leaflet. It provides hooks, factories, and utilities for building React components that wrap Leaflet classes.

Import from:

import { useLeafletContext, createLayerComponent, /* ... */ } from "@react-leaflet/core";

Capabilities

Context API

Access and manage the Leaflet context that connects React components to the map instance.

/**
 * Current context version number for compatibility checking
 * Used to ensure Core API compatibility between versions
 */
const CONTEXT_VERSION: 1;

/**
 * The Leaflet context interface containing map and layer references
 */
interface LeafletContextInterface {
  /** Version number for context compatibility */
  __version: number;
  /** The Leaflet Map instance */
  map: Map;
  /** Container for adding layers (LayerGroup or Control.Layers) */
  layerContainer?: Layer | LayerGroup;
  /** The layers control if present */
  layersControl?: Control.Layers;
  /** Container for div overlays (popups/tooltips) */
  overlayContainer?: Layer;
  /** Current pane name for rendering */
  pane?: string;
}

/**
 * React context object for accessing Leaflet instances
 */
const LeafletContext: Context<LeafletContextInterface | null>;

/**
 * Create a new Leaflet context from a map instance
 * @param map - The Leaflet Map instance
 * @returns New context interface
 */
function createLeafletContext(map: Map): LeafletContextInterface;

/**
 * Extend an existing context with additional properties
 * @param source - Base context to extend
 * @param extra - Additional properties to add
 * @returns New extended context
 */
function extendContext(
  source: LeafletContextInterface,
  extra: Partial<LeafletContextInterface>
): LeafletContextInterface;

/**
 * Hook to access the Leaflet context from within a component
 * @returns The current Leaflet context
 * @throws Error if used outside MapContainer
 */
function useLeafletContext(): LeafletContextInterface;

Usage Example:

import { useLeafletContext } from "@react-leaflet/core";

function CustomMapComponent() {
  const context = useLeafletContext();

  useEffect(() => {
    // Access the map instance
    console.log("Current zoom:", context.map.getZoom());

    // Access the current pane
    console.log("Current pane:", context.pane);
  }, [context]);

  return null;
}

Advanced Context Usage:

import { useLeafletContext, extendContext } from "@react-leaflet/core";

function CustomLayerGroup({ children, pane }) {
  const parentContext = useLeafletContext();
  
  // Create extended context with custom pane
  const childContext = useMemo(
    () => extendContext(parentContext, { pane }),
    [parentContext, pane]
  );

  return (
    <LeafletContext.Provider value={childContext}>
      {children}
    </LeafletContext.Provider>
  );
}

Element API

Core element types and factories for managing Leaflet instances within React components.

/**
 * Wrapper for a Leaflet instance with context and container references
 */
interface LeafletElement<T, C = unknown> {
  /** The Leaflet instance being wrapped */
  instance: T;
  /** The context this element belongs to */
  context: LeafletContextInterface;
  /** Optional container reference */
  container?: C | null;
}

/**
 * Create a LeafletElement wrapper object
 * @param instance - The Leaflet instance to wrap
 * @param context - The context to associate with
 * @param container - Optional container reference
 * @returns Wrapped element object
 */
function createElementObject<T, C = unknown>(
  instance: T,
  context: LeafletContextInterface,
  container?: C | null
): LeafletElement<T, C>;

/**
 * Hook factory signature for creating element hooks
 */
type ElementHook<E, P> = (props: P, context: LeafletContextInterface) => LeafletElement<E>;

/**
 * Create a hook for managing a Leaflet element
 * @param createElement - Function to create the Leaflet instance
 * @param updateElement - Optional function to update the instance
 * @returns Element hook
 */
function createElementHook<E, P, C = unknown>(
  createElement: (props: P, context: LeafletContextInterface) => LeafletElement<E, C>,
  updateElement?: (instance: E, props: P, prevProps: P) => void
): ElementHook<E, P>;

Usage Example:

import { createElementHook, createElementObject, LeafletContextInterface } from "@react-leaflet/core";
import L from "leaflet";

interface CustomLayerProps {
  center: [number, number];
  radius: number;
}

// Create element function
function createCustomLayer(props: CustomLayerProps, context: LeafletContextInterface) {
  const circle = L.circle(props.center, { radius: props.radius });
  return createElementObject(circle, context);
}

// Update element function
function updateCustomLayer(instance: L.Circle, props: CustomLayerProps, prevProps: CustomLayerProps) {
  if (props.center !== prevProps.center) {
    instance.setLatLng(props.center);
  }
  if (props.radius !== prevProps.radius) {
    instance.setRadius(props.radius);
  }
}

// Create the hook
const useCustomLayer = createElementHook(createCustomLayer, updateCustomLayer);

Event Handling

Utilities for managing Leaflet event handlers in React components.

/**
 * Props interface for components that support event handlers
 */
interface EventedProps {
  /** Map of event types to handler functions */
  eventHandlers?: LeafletEventHandlerFnMap;
}

/**
 * Hook to attach/detach event handlers to a Leaflet instance
 * @param element - The element to attach handlers to
 * @param eventHandlers - Map of event handlers
 */
function useEventHandlers(
  element: LeafletElement<Evented>,
  eventHandlers: LeafletEventHandlerFnMap | null | undefined
): void;

Usage Example:

import { useEventHandlers, createElementObject } from "@react-leaflet/core";

function CustomLayer({ eventHandlers }) {
  const context = useLeafletContext();
  const elementRef = useRef<LeafletElement<Layer>>();

  useEffect(() => {
    const layer = L.circle([51.505, -0.09], { radius: 200 });
    elementRef.current = createElementObject(layer, context);
    layer.addTo(context.map);

    return () => {
      layer.remove();
    };
  }, [context]);

  useEventHandlers(elementRef.current, eventHandlers);

  return null;
}

Advanced Event Handling:

function CustomInteractiveLayer({ eventHandlers, onReady }) {
  const context = useLeafletContext();
  const [element, setElement] = useState<LeafletElement<Layer> | null>(null);

  useEffect(() => {
    const layer = L.circle([51.505, -0.09], { radius: 200 });
    const el = createElementObject(layer, context);
    setElement(el);
    layer.addTo(context.map);
    
    onReady?.(layer);

    return () => {
      layer.remove();
    };
  }, [context, onReady]);

  // Event handlers are automatically updated when they change
  useEventHandlers(element, eventHandlers);

  return null;
}

Layer API

Types and hooks for creating layer components.

/**
 * Base props for layer components
 */
interface LayerProps extends EventedProps, LayerOptions {
  /** Pane name for rendering */
  pane?: string;
  /** Attribution text */
  attribution?: string;
}

/**
 * Props for interactive layers (shapes, markers, etc.)
 */
interface InteractiveLayerProps extends LayerProps, InteractiveLayerOptions {
  /** Whether layer responds to mouse/touch events */
  interactive?: boolean;
  /** Whether mouse events bubble to map */
  bubblingMouseEvents?: boolean;
}

/**
 * Create a hook for managing a layer's lifecycle
 * @param useElement - Element hook for the layer
 * @returns Layer hook with lifecycle management
 */
function createLayerHook<E extends Layer, P extends LayerProps>(
  useElement: ElementHook<E, P>
): ElementHook<E, P>;

/**
 * Hook to manage a layer's lifecycle (add/remove from map)
 * @param element - The layer element
 * @param context - The Leaflet context
 */
function useLayerLifecycle(
  element: LeafletElement<Layer>,
  context: LeafletContextInterface
): void;

Usage Example:

import { createLayerHook, useLayerLifecycle, createElementObject } from "@react-leaflet/core";

function useCustomLayerElement(props: CustomLayerProps, context: LeafletContextInterface) {
  const layer = useMemo(() => {
    return L.circle(props.center, { radius: props.radius });
  }, []);

  useEffect(() => {
    if (props.center) {
      layer.setLatLng(props.center);
    }
  }, [layer, props.center]);

  useEffect(() => {
    if (props.radius) {
      layer.setRadius(props.radius);
    }
  }, [layer, props.radius]);

  const element = useMemo(() => createElementObject(layer, context), [layer, context]);
  
  // Automatically handles adding/removing layer from map
  useLayerLifecycle(element, context);

  return element;
}

const useCustomLayer = createLayerHook(useCustomLayerElement);

Path API

Specialized APIs for path-based components (shapes with stroke/fill).

/**
 * Props for path components (shapes)
 */
interface PathProps extends InteractiveLayerProps {
  /** Path styling options */
  pathOptions?: PathOptions;
}

/**
 * Create a hook for managing a path component
 * @param useElement - Element hook for the path
 * @returns Path hook with style management
 */
function createPathHook<E extends Path | FeatureGroup, P extends PathProps>(
  useElement: ElementHook<E, P>
): ElementHook<E, P>;

/**
 * Hook to apply and update path styling options
 * @param element - The path element
 * @param props - Props containing pathOptions
 */
function usePathOptions(
  element: LeafletElement<Path | FeatureGroup>,
  props: PathProps
): void;

Usage Example:

import { createPathHook, usePathOptions } from "@react-leaflet/core";

function useCustomPathElement(props: PathProps, context: LeafletContextInterface) {
  const path = useMemo(() => L.circle(props.center, { radius: 100 }), [props.center]);
  const element = useMemo(() => createElementObject(path, context), [path, context]);
  
  // Automatically applies pathOptions styling
  usePathOptions(element, props);
  
  return element;
}

const useCustomPath = createPathHook(useCustomPathElement);

Circle Components

Props types and update functions for circle-based shapes.

/**
 * Props for CircleMarker component (radius in pixels)
 */
interface CircleMarkerProps extends CircleMarkerOptions, PathProps {
  /** Center point (required) */
  center: LatLngExpression;
  /** Child components */
  children?: ReactNode;
}

/**
 * Props for Circle component (radius in meters)
 */
interface CircleProps extends CircleOptions, PathProps {
  /** Center point (required) */
  center: LatLngExpression;
  /** Child components */
  children?: ReactNode;
}

/**
 * Update a circle's center and radius
 * @param layer - The circle or circle marker instance
 * @param props - New props
 * @param prevProps - Previous props
 */
function updateCircle<P extends CircleMarkerProps | CircleProps>(
  layer: CircleMarker | Circle,
  props: P,
  prevProps: P
): void;

Usage Example:

import { updateCircle } from "@react-leaflet/core";

function createCircleElement(props: CircleProps, context: LeafletContextInterface) {
  const circle = L.circle(props.center, { radius: props.radius });
  return createElementObject(circle, context);
}

function updateCircleElement(instance: L.Circle, props: CircleProps, prevProps: CircleProps) {
  // Use built-in updateCircle utility
  updateCircle(instance, props, prevProps);
}

Media Overlay API

Types and utilities for image/video/SVG overlays.

/**
 * Base props for media overlays
 */
interface MediaOverlayProps extends ImageOverlayOptions, InteractiveLayerProps {
  /** Geographic bounds (required) */
  bounds: LatLngBoundsExpression;
}

/**
 * Update a media overlay's bounds, URL, opacity, and zIndex
 * @param overlay - The overlay instance
 * @param props - New props
 * @param prevProps - Previous props
 */
function updateMediaOverlay<E extends ImageOverlay | VideoOverlay | SVGOverlay, P extends MediaOverlayProps>(
  overlay: E,
  props: P,
  prevProps: P
): void;

Usage Example:

import { updateMediaOverlay } from "@react-leaflet/core";

function updateImageOverlayElement(
  instance: L.ImageOverlay,
  props: ImageOverlayProps,
  prevProps: ImageOverlayProps
) {
  // Use built-in updateMediaOverlay utility
  updateMediaOverlay(instance, props, prevProps);
  
  // Additional custom updates
  if (props.customProp !== prevProps.customProp) {
    // Handle custom property
  }
}

Div Overlay API

Specialized APIs for popup and tooltip components.

/**
 * Type alias for div overlay instances
 */
type DivOverlay = Popup | Tooltip;

/**
 * Function signature for controlling overlay open state
 */
type SetOpenFunc = (open: boolean) => void;

/**
 * Hook signature for div overlay lifecycle management
 */
type DivOverlayLifecycleHook<E extends DivOverlay, P> = (
  element: LeafletElement<E>,
  context: LeafletContextInterface,
  props: P,
  setOpen: SetOpenFunc
) => void;

/**
 * Hook signature for div overlay components
 */
type DivOverlayHook<E extends DivOverlay, P> = (
  useElement: ElementHook<E, P>,
  useLifecycle: DivOverlayLifecycleHook<E, P>
) => ElementHook<E, P>;

/**
 * Create a hook for managing a div overlay (popup/tooltip)
 * @param useElement - Element hook
 * @param useLifecycle - Lifecycle hook
 * @returns Div overlay hook
 */
function createDivOverlayHook<E extends DivOverlay, P extends LayerProps>(
  useElement: ElementHook<E, P>,
  useLifecycle: DivOverlayLifecycleHook<E, P>
): ElementHook<E, P>;

Usage Example:

import { createDivOverlayHook } from "@react-leaflet/core";

function useCustomPopupLifecycle(
  element: LeafletElement<L.Popup>,
  context: LeafletContextInterface,
  props: PopupProps,
  setOpen: SetOpenFunc
) {
  useEffect(() => {
    const popup = element.instance;
    
    const onPopupOpen = () => setOpen(true);
    const onPopupClose = () => setOpen(false);
    
    popup.on('popupopen', onPopupOpen);
    popup.on('popupclose', onPopupClose);
    
    return () => {
      popup.off('popupopen', onPopupOpen);
      popup.off('popupclose', onPopupClose);
    };
  }, [element, setOpen]);
}

const useCustomPopup = createDivOverlayHook(useCustomPopupElement, useCustomPopupLifecycle);

Component Factories

High-level factories for creating different types of components.

/**
 * Create a container component that supports children
 * @param useElement - Element hook
 * @returns React component
 */
function createContainerComponent<E, P extends PropsWithChildren>(
  useElement: ElementHook<E, P>
): ForwardRefExoticComponent<P>;

/**
 * Create a div overlay component (popup/tooltip)
 * @param useElement - Element hook
 * @returns React component
 */
function createDivOverlayComponent<E extends DivOverlay, P extends PropsWithChildren>(
  useElement: ReturnType<DivOverlayHook<E, P>>
): FunctionComponent<P>;

/**
 * Create a leaf component (no children support)
 * @param useElement - Element hook
 * @returns React component
 */
function createLeafComponent<E, P>(
  useElement: ElementHook<E, P>
): FunctionComponent<P>;

/**
 * Create a control component
 * @param createInstance - Function to create control instance
 * @returns React component
 */
function createControlComponent<E extends Control, P extends ControlOptions>(
  createInstance: (props: P) => E
): FunctionComponent<P>;

/**
 * Create a layer component with children support
 * @param createElement - Function to create layer
 * @param updateElement - Optional update function
 * @returns React component
 */
function createLayerComponent<E extends Layer, P extends LayerProps & { children?: ReactNode }>(
  createElement: (props: P, context: LeafletContextInterface) => LeafletElement<E>,
  updateElement?: (instance: E, props: P, prevProps: P) => void
): FunctionComponent<P>;

/**
 * Create an overlay component (popup/tooltip)
 * @param createElement - Function to create overlay
 * @param useLifecycle - Lifecycle hook
 * @returns React component
 */
function createOverlayComponent<E extends DivOverlay, P extends LayerProps & { children?: ReactNode }>(
  createElement: (props: P, context: LeafletContextInterface) => LeafletElement<E>,
  useLifecycle: DivOverlayLifecycleHook<E, P>
): FunctionComponent<P>;

/**
 * Create a path component (shape with styling)
 * @param createElement - Function to create path
 * @param updateElement - Optional update function
 * @returns React component
 */
function createPathComponent<E extends Path | FeatureGroup, P extends PathProps & { children?: ReactNode }>(
  createElement: (props: P, context: LeafletContextInterface) => LeafletElement<E>,
  updateElement?: (instance: E, props: P, prevProps: P) => void
): FunctionComponent<P>;

/**
 * Create a tile layer component
 * @param createElement - Function to create tile layer
 * @param updateElement - Optional update function
 * @returns React component
 */
function createTileLayerComponent<E extends TileLayer | TileLayer.WMS, P extends LayerProps>(
  createElement: (props: P, context: LeafletContextInterface) => LeafletElement<E>,
  updateElement?: (instance: E, props: P, prevProps: P) => void
): FunctionComponent<P>;

Usage Example - Creating a Custom Component:

import { createLayerComponent, LeafletElement, LeafletContextInterface } from "@react-leaflet/core";
import L from "leaflet";

interface CustomCircleProps {
  center: [number, number];
  radius: number;
  color?: string;
}

// Create element function
function createCustomCircle(
  props: CustomCircleProps,
  context: LeafletContextInterface
): LeafletElement<L.Circle> {
  const circle = L.circle(props.center, {
    radius: props.radius,
    color: props.color || "blue",
  });

  return { instance: circle, context };
}

// Update element function
function updateCustomCircle(
  instance: L.Circle,
  props: CustomCircleProps,
  prevProps: CustomCircleProps
) {
  if (props.center !== prevProps.center) {
    instance.setLatLng(props.center);
  }
  if (props.radius !== prevProps.radius) {
    instance.setRadius(props.radius);
  }
  if (props.color !== prevProps.color) {
    instance.setStyle({ color: props.color });
  }
}

// Create the component
const CustomCircle = createLayerComponent(createCustomCircle, updateCustomCircle);

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

Control Utilities

Utilities for creating control components.

/**
 * Create a hook for managing a control's lifecycle
 * @param useElement - Element hook for the control
 * @returns Control hook
 */
function createControlHook<E extends Control, P extends ControlOptions>(
  useElement: ElementHook<E, P>
): ElementHook<E, P>;

Usage Example:

import { createControlHook, createControlComponent } from "@react-leaflet/core";

function useCustomControlElement(props: CustomControlProps, context: LeafletContextInterface) {
  const control = useMemo(() => {
    return new CustomControl(props);
  }, []);

  useEffect(() => {
    control.addTo(context.map);
    return () => {
      control.remove();
    };
  }, [control, context.map]);

  return createElementObject(control, context);
}

const useCustomControl = createControlHook(useCustomControlElement);
const CustomControlComponent = createControlComponent((props) => new CustomControl(props));

DOM Utilities

Helper functions for managing CSS class names on DOM elements.

/**
 * Add a CSS class to an element
 * @param element - HTML element
 * @param className - Class name to add
 */
function addClassName(element: HTMLElement, className: string): void;

/**
 * Remove a CSS class from an element
 * @param element - HTML element
 * @param className - Class name to remove
 */
function removeClassName(element: HTMLElement, className: string): void;

/**
 * Update an element's class name
 * @param element - HTML element
 * @param prevClassName - Previous class name
 * @param nextClassName - New class name
 */
function updateClassName(
  element?: HTMLElement,
  prevClassName?: string,
  nextClassName?: string
): void;

Usage Example:

import { addClassName, removeClassName, updateClassName } from "@react-leaflet/core";

function CustomComponent({ className }) {
  const elementRef = useRef<HTMLElement>(null);

  useEffect(() => {
    if (elementRef.current && className) {
      addClassName(elementRef.current, className);
      return () => {
        if (elementRef.current) {
          removeClassName(elementRef.current, className);
        }
      };
    }
  }, [className]);

  return <div ref={elementRef}>Custom content</div>;
}

Pane Utilities

Utilities for working with Leaflet panes.

/**
 * Apply pane from context to props if not specified
 * @param props - Props that may contain pane
 * @param context - Context containing pane
 * @returns Props with pane applied
 */
function withPane<P extends LayerOptions>(
  props: P,
  context: LeafletContextInterface
): P;

Usage Example:

import { withPane } from "@react-leaflet/core";

function createLayerWithPane(props: LayerProps, context: LeafletContextInterface) {
  // Automatically applies pane from context if not specified in props
  const propsWithPane = withPane(props, context);
  
  const layer = L.circle([51.505, -0.09], {
    ...propsWithPane,
    radius: 200,
  });
  
  return createElementObject(layer, context);
}

Attribution Utilities

Hook for managing map attribution.

/**
 * Hook to add/remove attribution from the map
 * @param map - Map instance
 * @param attribution - Attribution string
 */
function useAttribution(
  map: Map,
  attribution: string | null | undefined
): void;

Usage Example:

import { useAttribution } from "@react-leaflet/core";

function CustomLayer({ attribution }) {
  const context = useLeafletContext();
  
  // Automatically adds/removes attribution
  useAttribution(context.map, attribution);
  
  return null;
}

Grid Layer Utilities

Update functions for tile layers.

/**
 * Update grid layer properties
 * @param layer - Grid layer instance
 * @param props - New props
 * @param prevProps - Previous props
 */
function updateGridLayer<E extends GridLayer, P extends GridLayerOptions>(
  layer: E,
  props: P,
  prevProps: P
): void;

Usage Example:

import { updateGridLayer } from "@react-leaflet/core";

function updateCustomTileLayer(
  instance: L.TileLayer,
  props: TileLayerProps,
  prevProps: TileLayerProps
) {
  // Use built-in updateGridLayer utility
  updateGridLayer(instance, props, prevProps);
  
  // Handle URL changes
  if (props.url !== prevProps.url) {
    instance.setUrl(props.url);
  }
}

When to Use Core APIs

The @react-leaflet/core APIs are intended for advanced use cases:

  1. Creating Custom Components: When you need to wrap Leaflet plugins or custom layers
  2. Extending Functionality: When building libraries on top of react-leaflet
  3. Performance Optimization: When you need fine-grained control over component lifecycle
  4. Custom Integrations: When integrating with other mapping libraries or custom Leaflet code
  5. Plugin Wrappers: When creating React wrappers for Leaflet plugins
  6. Advanced State Management: When you need custom context or state handling

For standard mapping needs, use the high-level components from react-leaflet instead.

Important Notes

  1. Internal APIs: Many core APIs are considered internal implementation details. Use with caution as they may change between versions.

  2. Context Requirement: Most hooks must be used within components that are children of MapContainer.

  3. Lifecycle Management: When creating custom components, ensure proper cleanup in useEffect return functions.

  4. Type Safety: All factories and hooks maintain full TypeScript type safety for Leaflet instances.

  5. Version Compatibility: The CONTEXT_VERSION constant helps ensure compatibility between different versions.

  6. Memory Leaks: Always remove event listeners and layers in cleanup functions to prevent memory leaks.

  7. Ref Forwarding: Use ForwardRefExoticComponent for components that need ref support.

  8. Update Functions: Update functions should only modify properties that have changed (compare with prevProps).

  9. Context Extension: When extending context, always use extendContext to maintain immutability.

  10. Performance: Minimize re-renders by using useMemo for element creation and useCallback for event handlers.

Examples

Custom Marker with Custom Icon

import { createLayerComponent, LeafletElement, LeafletContextInterface } from "@react-leaflet/core";
import L from "leaflet";

interface CustomMarkerProps {
  position: [number, number];
  text: string;
}

function createCustomMarker(
  props: CustomMarkerProps,
  context: LeafletContextInterface
): LeafletElement<L.Marker> {
  const icon = L.divIcon({
    html: `<div class="custom-marker">${props.text}</div>`,
    className: "",
  });

  const marker = L.marker(props.position, { icon });
  return { instance: marker, context };
}

function updateCustomMarker(
  instance: L.Marker,
  props: CustomMarkerProps,
  prevProps: CustomMarkerProps
) {
  if (props.position !== prevProps.position) {
    instance.setLatLng(props.position);
  }
  if (props.text !== prevProps.text) {
    const icon = L.divIcon({
      html: `<div class="custom-marker">${props.text}</div>`,
    });
    instance.setIcon(icon);
  }
}

const CustomMarker = createLayerComponent(createCustomMarker, updateCustomMarker);

Custom Control Component

import { createControlComponent } from "@react-leaflet/core";
import L from "leaflet";

interface CustomControlProps {
  position?: "topleft" | "topright" | "bottomleft" | "bottomright";
  text: string;
}

const CustomControl = L.Control.extend({
  onAdd: function(map) {
    const container = L.DomUtil.create("div", "custom-control");
    container.innerHTML = this.options.text;
    return container;
  },
});

const CustomControlComponent = createControlComponent<L.Control, CustomControlProps>(
  (props) => new CustomControl(props)
);

Accessing Context in Custom Component

import { useLeafletContext } from "@react-leaflet/core";

function MapInfo() {
  const context = useLeafletContext();
  const [zoom, setZoom] = useState(context.map.getZoom());

  useEffect(() => {
    const updateZoom = () => setZoom(context.map.getZoom());
    context.map.on("zoomend", updateZoom);

    return () => {
      context.map.off("zoomend", updateZoom);
    };
  }, [context.map]);

  return <div className="map-info">Zoom: {zoom}</div>;
}

Custom Layer with Event Handlers

import { createLayerComponent, useEventHandlers } from "@react-leaflet/core";

interface AnimatedCircleProps extends PathProps {
  center: LatLngExpression;
  radius: number;
  animationDuration?: number;
}

function createAnimatedCircle(
  props: AnimatedCircleProps,
  context: LeafletContextInterface
): LeafletElement<L.Circle> {
  const circle = L.circle(props.center, {
    radius: props.radius,
    ...props.pathOptions,
  });

  // Add custom animation
  if (props.animationDuration) {
    circle.on('add', () => {
      circle.setStyle({ fillOpacity: 0 });
      setTimeout(() => {
        circle.setStyle({ fillOpacity: 0.5 });
      }, props.animationDuration);
    });
  }

  return { instance: circle, context };
}

function updateAnimatedCircle(
  instance: L.Circle,
  props: AnimatedCircleProps,
  prevProps: AnimatedCircleProps
) {
  if (props.center !== prevProps.center) {
    instance.setLatLng(props.center);
  }
  if (props.radius !== prevProps.radius) {
    instance.setRadius(props.radius);
  }
}

const AnimatedCircle = createLayerComponent(createAnimatedCircle, updateAnimatedCircle);

Wrapping a Leaflet Plugin

import { createLayerComponent } from "@react-leaflet/core";
import "leaflet-heat"; // Hypothetical heatmap plugin

interface HeatmapLayerProps {
  points: Array<[number, number, number]>; // [lat, lng, intensity]
  options?: any;
}

function createHeatmapLayer(
  props: HeatmapLayerProps,
  context: LeafletContextInterface
): LeafletElement<any> {
  const heatLayer = (L as any).heatLayer(props.points, props.options);
  return { instance: heatLayer, context };
}

function updateHeatmapLayer(
  instance: any,
  props: HeatmapLayerProps,
  prevProps: HeatmapLayerProps
) {
  if (props.points !== prevProps.points) {
    instance.setLatLngs(props.points);
  }
  if (props.options !== prevProps.options) {
    instance.setOptions(props.options);
  }
}

const HeatmapLayer = createLayerComponent(createHeatmapLayer, updateHeatmapLayer);

Custom Pane Component

import { useLeafletContext, extendContext, LeafletContext } from "@react-leaflet/core";

interface CustomPaneProps {
  name: string;
  zIndex?: number;
  children: ReactNode;
}

function CustomPane({ name, zIndex, children }: CustomPaneProps) {
  const parentContext = useLeafletContext();
  const paneRef = useRef<HTMLElement | null>(null);

  useEffect(() => {
    const map = parentContext.map;
    
    // Create pane if it doesn't exist
    if (!map.getPane(name)) {
      const pane = map.createPane(name);
      if (zIndex !== undefined) {
        pane.style.zIndex = String(zIndex);
      }
      paneRef.current = pane;
    } else {
      paneRef.current = map.getPane(name);
    }

    return () => {
      // Optionally remove pane on unmount
      // Note: Leaflet doesn't provide a removePane method
    };
  }, [parentContext.map, name, zIndex]);

  const childContext = useMemo(
    () => extendContext(parentContext, { pane: name }),
    [parentContext, name]
  );

  return (
    <LeafletContext.Provider value={childContext}>
      {children}
    </LeafletContext.Provider>
  );
}

Best Practices

  1. Memoization: Use useMemo for expensive computations and element creation
  2. Cleanup: Always clean up resources in useEffect return functions
  3. Type Safety: Leverage TypeScript for better development experience
  4. Performance: Avoid unnecessary re-renders by comparing props in update functions
  5. Documentation: Document custom components thoroughly for maintainability
  6. Testing: Write tests for custom components to ensure reliability
  7. Error Handling: Add proper error handling for edge cases
  8. Accessibility: Consider accessibility when creating custom UI controls
  9. Responsive Design: Ensure custom components work on different screen sizes
  10. Browser Compatibility: Test custom components across different browsers

Testing Custom Components

Unit Testing Custom Layer

import { render } from '@testing-library/react';
import { MapContainer } from 'react-leaflet';
import { createLayerComponent } from '@react-leaflet/core';

// Your custom component
const CustomLayer = createLayerComponent(
  (props, context) => {
    const layer = L.circle(props.center, { radius: props.radius });
    return { instance: layer, context };
  }
);

test('renders custom layer component', () => {
  const { container } = render(
    <MapContainer center={[51.505, -0.09]} zoom={13}>
      <CustomLayer center={[51.505, -0.09]} radius={500} />
    </MapContainer>
  );
  
  expect(container).toBeInTheDocument();
});

Testing Custom Control

import { createControlComponent } from '@react-leaflet/core';
import L from 'leaflet';

const CustomControl = L.Control.extend({
  onAdd: function() {
    const container = L.DomUtil.create('div', 'custom-control');
    container.innerHTML = 'Custom Control';
    return container;
  },
});

const CustomControlComponent = createControlComponent(
  (props) => new CustomControl(props)
);

test('renders custom control', () => {
  render(
    <MapContainer center={[51.505, -0.09]} zoom={13}>
      <CustomControlComponent position="topright" />
    </MapContainer>
  );
  
  // Add assertions
});

Type-Safe Component Creation

Creating Type-Safe Custom Components

import { createLayerComponent, LeafletContextInterface, LeafletElement } from '@react-leaflet/core';
import type { Circle as LeafletCircle, CircleOptions } from 'leaflet';
import L from 'leaflet';

interface CustomCircleProps extends CircleOptions {
  center: [number, number];
  radius: number;
  color?: string;
}

function createCustomCircle(
  props: CustomCircleProps,
  context: LeafletContextInterface
): LeafletElement<LeafletCircle> {
  const circle = L.circle(props.center, {
    radius: props.radius,
    color: props.color || 'blue',
    ...props,
  });

  return {
    instance: circle,
    context,
  };
}

function updateCustomCircle(
  instance: LeafletCircle,
  props: CustomCircleProps,
  prevProps: CustomCircleProps
): void {
  if (props.center !== prevProps.center) {
    instance.setLatLng(props.center);
  }
  if (props.radius !== prevProps.radius) {
    instance.setRadius(props.radius);
  }
  if (props.color !== prevProps.color) {
    instance.setStyle({ color: props.color });
  }
}

export const CustomCircle = createLayerComponent<LeafletCircle, CustomCircleProps>(
  createCustomCircle,
  updateCustomCircle
);

Advanced Patterns

Component with Lifecycle Hooks

import { createLayerComponent, useLayerLifecycle } from '@react-leaflet/core';
import { useEffect, useMemo } from 'react';

function useAdvancedLayer(props: AdvancedLayerProps, context: LeafletContextInterface) {
  const layer = useMemo(() => {
    return L.circle(props.center, { radius: props.radius });
  }, [props.center, props.radius]);

  const element = useMemo(
    () => ({ instance: layer, context }),
    [layer, context]
  );

  // Lifecycle management
  useLayerLifecycle(element, context);

  // Custom effect for specific prop changes
  useEffect(() => {
    if (props.highlight) {
      layer.setStyle({ fillColor: 'yellow' });
    } else {
      layer.setStyle({ fillColor: 'blue' });
    }
  }, [layer, props.highlight]);

  return element;
}

export const AdvancedLayer = createLayerComponent(useAdvancedLayer);

Accessibility in Custom Components

Creating Accessible Custom Controls

import { createControlComponent } from '@react-leaflet/core';
import L from 'leaflet';

interface AccessibleControlProps {
  position?: 'topleft' | 'topright' | 'bottomleft' | 'bottomright';
  label: string;
  onClick: () => void;
}

const AccessibleControl = L.Control.extend({
  onAdd: function(map) {
    const container = L.DomUtil.create('div', 'accessible-control');
    const button = L.DomUtil.create('button', '', container);
    
    button.innerHTML = this.options.label;
    button.setAttribute('aria-label', this.options.label);
    button.setAttribute('role', 'button');
    button.setAttribute('type', 'button');
    
    L.DomEvent.on(button, 'click', (e) => {
      L.DomEvent.stopPropagation(e);
      L.DomEvent.preventDefault(e);
      this.options.onClick();
    });
    
    return container;
  },
});

export const AccessibleControlComponent = createControlComponent<
  L.Control,
  AccessibleControlProps
>((props) => new AccessibleControl(props));

Troubleshooting

Context Not Available

  • Ensure component is rendered inside MapContainer
  • Check that MapContainer has been initialized
  • Verify context version compatibility

Memory Leaks

  • Always remove event listeners in cleanup functions
  • Remove layers from map when component unmounts
  • Clear references to Leaflet instances

Update Not Working

  • Verify update function is comparing props correctly
  • Check that props are actually changing (use React DevTools)
  • Ensure Leaflet instance methods are being called correctly

TypeScript Errors

  • Import types from @react-leaflet/core and leaflet
  • Use proper generic parameters in factory functions
  • Ensure props interfaces extend appropriate base types