tessl install tessl/npm-react-leaflet@5.0.3React components for Leaflet maps
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.
Before using Core APIs, you should be familiar with:
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";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>
);
}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);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;
}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);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);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);
}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
}
}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);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>
);
}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));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>;
}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);
}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;
}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);
}
}The @react-leaflet/core APIs are intended for advanced use cases:
For standard mapping needs, use the high-level components from react-leaflet instead.
Internal APIs: Many core APIs are considered internal implementation details. Use with caution as they may change between versions.
Context Requirement: Most hooks must be used within components that are children of MapContainer.
Lifecycle Management: When creating custom components, ensure proper cleanup in useEffect return functions.
Type Safety: All factories and hooks maintain full TypeScript type safety for Leaflet instances.
Version Compatibility: The CONTEXT_VERSION constant helps ensure compatibility between different versions.
Memory Leaks: Always remove event listeners and layers in cleanup functions to prevent memory leaks.
Ref Forwarding: Use ForwardRefExoticComponent for components that need ref support.
Update Functions: Update functions should only modify properties that have changed (compare with prevProps).
Context Extension: When extending context, always use extendContext to maintain immutability.
Performance: Minimize re-renders by using useMemo for element creation and useCallback for event handlers.
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);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)
);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>;
}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);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);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>
);
}useMemo for expensive computations and element creationimport { 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();
});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
});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
);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);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));