React Leaflet Error with HMR – Cannot read properties of undefined

Summary

This incident documents a React‑Leaflet crash during Hot Module Reloading (HMR) in a Next.js 16 application. The error surfaced as:

“Cannot read properties of undefined (reading ‘appendChild’)”

The failure occurred only during HMR updates, not on initial load, and was triggered by a dynamically imported, client‑only React‑Leaflet map component.

Root Cause

The underlying issue is that React‑Leaflet and Leaflet maintain imperative DOM state, and HMR disrupts that state mid‑lifecycle.

Key root causes:

  • Leaflet mutates the DOM directly, outside React’s reconciliation model.
  • HMR replaces React components without resetting Leaflet instances, leaving orphaned map objects.
  • MapContainer is destroyed and recreated, but Leaflet’s internal references (e.g., _container, _mapPane) still point to stale DOM nodes.
  • When React‑Leaflet tries to re‑attach layers, Leaflet attempts appendChild on a DOM node that no longer exists.

Why This Happens in Real Systems

This class of bug is common when:

  • Libraries use imperative DOM manipulation (Leaflet, D3, Mapbox GL).
  • Frameworks use client‑side HMR (Next.js, Vite, CRA).
  • Components are dynamically imported and re‑mounted frequently.
  • The library does not support HMR or has partial support.

In these cases, HMR reloads the React component tree, but the underlying library still holds references to DOM nodes that React has replaced.

Real-World Impact

This failure can cause:

  • Broken development workflow due to constant HMR crashes.
  • Memory leaks from orphaned Leaflet map instances.
  • Duplicate map containers or “Map container is already initialized” errors.
  • Inconsistent UI state after hot reloads.

Example or Code (if necessary and relevant)

A safe pattern is to ensure Leaflet is initialized only once and never re‑initialized during HMR:

import { useEffect } from "react";
import L from "leaflet";

let defaultIcon: L.Icon | null = null;

export function useLeafletIcon() {
  useEffect(() => {
    if (!defaultIcon) {
      defaultIcon = L.icon({
        iconUrl: "/map/marker-icon.png",
        shadowUrl: "/map/marker-shadow.png",
      });
      L.Marker.prototype.options.icon = defaultIcon;
    }
  }, []);
}

This prevents HMR from re‑running Leaflet initialization logic.

How Senior Engineers Fix It

Experienced engineers typically apply one or more of these strategies:

  • Isolate Leaflet initialization so it runs only once per browser session.
  • Wrap MapContainer in a stable parent component that does not unmount during HMR.
  • Avoid dynamic imports for Leaflet components unless absolutely necessary.
  • Use a defensive check to destroy existing Leaflet instances before re‑creating them.
  • Disable HMR for the map component when the library is known to be incompatible.
  • Move Leaflet icon setup outside React (module‑level singleton).

Why Juniors Miss It

Less experienced developers often overlook:

  • That Leaflet is not a pure React library and maintains its own DOM.
  • That HMR does not guarantee cleanup of external state.
  • That dynamic imports can cause extra unmount/mount cycles.
  • That React‑Leaflet’s abstraction does not fully protect against Leaflet’s imperative internals.
  • That errors like appendChild usually indicate DOM lifecycle mismatches, not missing elements.

They assume React controls everything, but Leaflet lives outside React’s lifecycle, making it vulnerable to HMR disruptions.

Leave a Comment