mrkeyoor.com_
Sat 08 Aug 21:57 UTC
npmWeb Frontendupdated 08 Aug 2026

@react-leaflet/core

`@react-leaflet/core` is the low-level construction kit behind React Leaflet 5. It exposes React context, lifecycle hooks, update helpers, and component factories for turning Leaflet classes or third-party Leaflet plugins into declarative React components. Most application developers should install and use `react-leaflet` instead; this package is for adapter authors and teams whose custom map behavior does not fit the public components.

Verdict

Use this package to publish serious React 19 adapters for Leaflet plugins; its factories encode the lifecycle details that are easy to get wrong. Application code that just needs a map should stay on `react-leaflet`, and React 18 or CommonJS projects should not force core 3 into the build.

API stability3/5The factory model has persisted across recent React Leaflet generations, and version 3 exposes a compact typed surface for elements, layers, paths, controls, overlays, context, and shared update functions. Major upgrades are meaningful, however: React Leaflet 4 removed CommonJS and several public hooks, while React Leaflet 5 made React 19 mandatory and removed `LeafletProvider` from core. Adapter authors must track majors closely.
Docs4/5The official site separates the intended audience from normal React Leaflet users, walks a square layer from manual effects through element and path factories, and publishes signatures for every context utility, hook, updater, and component factory. That architecture tutorial is unusually useful. It still assumes solid Leaflet and React knowledge, and plugin-specific cleanup or SSR packaging gets little step-by-step treatment.
Maintenance3/5Core 3.0.0 shipped with React Leaflet 5 in December 2024, and the repository was pushed in December 2025, so this is maintained software rather than an abandoned compatibility shim. The shared repository currently has 45 open issues when pull requests are excluded, and there has not been a newer stable core release through the measured date. Development is real but paced, with breaking work concentrated in major releases.
Ecosystem4/5The package recorded 3,492,862 downloads in the measured week and inherits the reach of React Leaflet, whose repository has 5,598 stars. Its main value is access to Leaflet's long-standing plugin ecosystem: a plugin class can be adapted without forking React Leaflet. The audience is still narrower than the download number suggests because every `react-leaflet` installation depends on core, so many downloads are not direct API users.

Use it if

  • You are wrapping a third-party Leaflet layer or control as a reusable React component
  • You need React Leaflet to add, update, and remove a custom Leaflet object with the same lifecycle as built-in layers
  • You are building a component library on React 19, Leaflet 1.9, and React Leaflet 5
  • You need direct access to the map, parent layer container, pane, or overlay context below `MapContainer`
Skip it if

Setup reality

Install `@react-leaflet/core`, `react-leaflet`, `leaflet`, `react`, and `react-dom` in compatible generations. Core 3.0.0 requires Leaflet `^1.9.0` plus React and React DOM `^19.0.0`; npm can warn or fail when an existing React 18 application tries to add it. Leaflet 1.9.4 does not publish its own TypeScript declarations, while core's declarations import Leaflet types, so TypeScript projects normally add `@types/leaflet` as a development dependency. The package ships ESM only. Jest, older bundlers, and server code configured for CommonJS need ESM handling instead of `require()`. Importing map code during server rendering is another common surprise because Leaflet is browser and DOM oriented; put the map behind the framework's client boundary and, where necessary, load it only in the browser. Core does not replace the usual Leaflet setup: the map container needs an explicit height and normal Leaflet UI still needs `leaflet/dist/leaflet.css`. Every core hook also assumes React Leaflet context. `useLeafletContext()` throws when called outside a descendant of `MapContainer`. For a wrapper, creation runs once, so props only become mutable if you supply an update function. Factories handle standard add/remove, events, paths, panes, and refs, but plugin-specific listeners, workers, canvases, or DOM nodes remain your cleanup responsibility. Finally, review the Hippocratic 2.1 license before distributing a product built on it; do not assume it has the same approval profile as Leaflet's BSD license.

Patterns

Turn a Leaflet control into a React componentwrap-leaflet-control

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

export const ZoomControl = createControlComponent<
  Control.Zoom,
  Control.ZoomOptions
>((props) => new Control.Zoom(props));

The control factory adds and removes the instance and updates its position. Render it below `MapContainer`, not as a standalone component.

Create an updatable path componentwrap-custom-path

import { createElementObject, createPathComponent, type PathProps } from '@react-leaflet/core';
import { Rectangle, latLng, type LatLngExpression } from 'leaflet';

type SquareProps = PathProps & { center: LatLngExpression; size: number };
const bounds = (p: SquareProps) => latLng(p.center).toBounds(p.size);

export const Square = createPathComponent<Rectangle, SquareProps>(
  (props, context) => createElementObject(
    new Rectangle(bounds(props), props.pathOptions), context
  ),
  (layer, props, prev) => {
    if (props.center !== prev.center || props.size !== prev.size) {
      layer.setBounds(bounds(props));
    }
  }
);

Creation runs once. The second callback is what makes `center` and `size` changes reach the existing Leaflet rectangle.

Wrap a custom tile source with mutable optionswrap-tile-layer

import { createElementObject, createTileLayerComponent, updateGridLayer, type LayerProps } from '@react-leaflet/core';
import { TileLayer, type TileLayerOptions } from 'leaflet';

type Props = LayerProps & TileLayerOptions & { url: string };
export const CustomTiles = createTileLayerComponent<TileLayer, Props>(
  ({ url, eventHandlers, ...options }, context) =>
    createElementObject(new TileLayer(url, options), context),
  (layer, props, prev) => {
    updateGridLayer(layer, props, prev);
    if (props.url !== prev.url) layer.setUrl(props.url);
  }
);

`updateGridLayer` only covers opacity and z-index. Update the URL yourself, and keep React-only props out of Leaflet's constructor options.

Access the current Leaflet map in a custom hookread-map-context

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

export function useFitOnLoad() {
  const { map } = useLeafletContext();
  useEffect(() => {
    map.fitBounds([[40.70, -74.02], [40.88, -73.90]]);
  }, [map]);
}

The hook throws when no React Leaflet context exists, so call it only from a component rendered beneath `MapContainer`.

Build a layer from low-level hooksmanage-layer-lifecycle

import { createElementHook, createElementObject, useLayerLifecycle, useLeafletContext } from '@react-leaflet/core';
import { Marker, type LatLngExpression } from 'leaflet';

const useMarkerElement = createElementHook(
  ({ position }: { position: LatLngExpression }, context) =>
    createElementObject(new Marker(position), context),
  (marker, props, prev) => {
    if (props.position !== prev.position) marker.setLatLng(props.position);
  }
);

export function BareMarker(props: { position: LatLngExpression }) {
  const context = useLeafletContext();
  const element = useMarkerElement(props, context);
  useLayerLifecycle(element.current, context);
  return null;
}

Prefer `createLayerComponent` for ordinary wrappers. These lower-level pieces are useful only when your component lifecycle needs extra behavior.

Call Leaflet methods through a forwarded refforward-layer-ref

import { useEffect, useRef } from 'react';
import type { Rectangle } from 'leaflet';

function Selection() {
  const rectangleRef = useRef<Rectangle>(null);
  useEffect(() => {
    rectangleRef.current?.bringToFront();
  }, []);
  return <Square ref={rectangleRef} center={[51.5, -0.09]} size={500} />;
}

Components created by the core factories forward the underlying Leaflet instance, not a DOM element.

Expose Leaflet events through component propshandle-layer-events

<Square
  center={[51.5, -0.09]}
  size={500}
  pathOptions={{ color: 'tomato' }}
  eventHandlers={{
    click: (event) => console.log(event.latlng),
    mouseover: (event) => event.target.setStyle({ weight: 6 }),
  }}
/>

`createPathComponent` wires `eventHandlers` on mount and removes the previous handler map when the prop changes or the component unmounts.

Route child layers into a custom groupprovide-child-container

import { createElementObject, createLayerComponent, extendContext, type LayerProps } from '@react-leaflet/core';
import { FeatureGroup } from 'leaflet';
import type { PropsWithChildren } from 'react';

type GroupProps = LayerProps & PropsWithChildren;
export const PluginGroup = createLayerComponent<FeatureGroup, GroupProps>(
  (props, context) => {
    const group = new FeatureGroup();
    return createElementObject(
      group,
      extendContext(context, { layerContainer: group })
    );
  }
);

The extended context makes nested React Leaflet layers attach to the group. Context objects are frozen, so create an extension instead of mutating the parent.

Reuse the shared media overlay updaterupdate-media-overlay

import { createElementObject, createLayerComponent, updateMediaOverlay, type MediaOverlayProps } from '@react-leaflet/core';
import { ImageOverlay } from 'leaflet';

type Props = MediaOverlayProps & { url: string };
export const PhotoOverlay = createLayerComponent<ImageOverlay, Props>(
  ({ url, bounds, eventHandlers, children, ...options }, context) =>
    createElementObject(new ImageOverlay(url, bounds, options), context),
  (overlay, props, prev) => {
    updateMediaOverlay(overlay, props, prev);
    if (props.url !== prev.url) overlay.setUrl(props.url);
  }
);

The helper updates bounds, opacity, and z-index, but it does not update your custom URL prop.

Replace classes on plugin-owned DOMupdate-control-classes

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

function syncControlTheme(
  container: HTMLElement,
  previousTheme?: string,
  nextTheme?: string
) {
  updateClassName(container, previousTheme, nextTheme);
}

The helper splits space-separated class strings and uses Leaflet's DOM utility. Pass the previous and next values or stale classes remain.

Alternatives

PackageRegistryPick it when
react-leafletnpmYou are building a map screen and need the supported public components rather than adapter internals
react-map-glnpmYou need React bindings for Mapbox GL or MapLibre vector maps instead of the Leaflet plugin ecosystem
@react-google-maps/apinpmYour product is committed to Google Maps services and wants React components for that API