Skip to content

getProjection() in @deck.gl/mapbox can throw instead of returning undefined when called before the map's style is assigned #10549

Description

@nehulparekh

Description

getProjection() in @deck.gl/mapbox's deck-utils.ts can throw when called
before the underlying map's style is assigned, instead of returning
undefined the way its callers (e.g. MapboxOverlay#_onAddInterleaved /
getDefaultView) expect for "not ready yet, default to mercator".

export function getProjection(map: Map): 'mercator' | 'globe' {
  const projection = map.getProjection?.();
  // ...

This guards against the method being absent (?.), but not against the
call itself throwing.

Repro

  • @deck.gl/mapbox@9.3.9, maplibre-gl@5.24, react-map-gl (maplibre entry
    point), React 19, MapboxOverlay in interleaved mode via useControl.
  • Under React 19 StrictMode, the dev-only double-invoke of mount effects
    adds, removes, and re-adds the same overlay instance within a single mount.
    On the third addControl call, map.getProjection() is invoked before the
    map's style has been attached.
  • maplibre-gl@5.24's own Map.prototype.getProjection() is
    return this.style.getProjection(); with no guard on this.style — so
    calling it before the style exists throws
    (Cannot read properties of undefined (reading 'getProjection')) rather
    than returning undefined.
  • Since @deck.gl/mapbox's getProjection() only guards the method being
    absent, the throw propagates out of _onAddInterleaved, surfacing as an
    uncaught error in the host application.

Reproduced live with Playwright (response-body interception instrumenting
getProjection): with StrictMode active, exactly 3 getProjection calls
fire on mount, and the 3rd hits a still-styleless map.

Related

PR #9794 fixed a similar timing/undefined issue in the later
_handleStyleChange callback path ("getDefaultView is called too early,
and getProjection returns undefined... the bug only happens with React,
in interleaved mode"), but the fix doesn't cover this earlier onAdd-time
call site, which can throw rather than return undefined.

Suggested fix

Wrap the map.getProjection?.() call in a try/catch inside getProjection(),
treating a throw the same as the already-handled undefined case:

export function getProjection(map: Map): 'mercator' | 'globe' {
  let projection;
  try {
    projection = map.getProjection?.();
  } catch {
    projection = undefined;
  }
  const type =
    // maplibre projection spec
    projection?.type ||
    // ...

We've worked around this locally with a pnpm patch applying exactly this
change to @deck.gl/mapbox@9.3.9 and would be happy to open a PR with it if
that's useful.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions