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.
Description
getProjection()in@deck.gl/mapbox'sdeck-utils.tscan throw when calledbefore the underlying map's style is assigned, instead of returning
undefinedthe way its callers (e.g.MapboxOverlay#_onAddInterleaved/getDefaultView) expect for "not ready yet, default to mercator".This guards against the method being absent (
?.), but not against thecall itself throwing.
Repro
@deck.gl/mapbox@9.3.9,maplibre-gl@5.24,react-map-gl(maplibre entrypoint), React 19,
MapboxOverlayin interleaved mode viauseControl.adds, removes, and re-adds the same overlay instance within a single mount.
On the third
addControlcall,map.getProjection()is invoked before themap's style has been attached.
maplibre-gl@5.24's ownMap.prototype.getProjection()isreturn this.style.getProjection();with no guard onthis.style— socalling it before the style exists throws
(
Cannot read properties of undefined (reading 'getProjection')) ratherthan returning
undefined.@deck.gl/mapbox'sgetProjection()only guards the method beingabsent, the throw propagates out of
_onAddInterleaved, surfacing as anuncaught error in the host application.
Reproduced live with Playwright (response-body interception instrumenting
getProjection): with StrictMode active, exactly 3getProjectioncallsfire on mount, and the 3rd hits a still-styleless map.
Related
PR #9794 fixed a similar timing/undefined issue in the later
_handleStyleChangecallback path ("getDefaultViewis called too early,and
getProjectionreturnsundefined... the bug only happens with React,in interleaved mode"), but the fix doesn't cover this earlier
onAdd-timecall site, which can throw rather than return
undefined.Suggested fix
Wrap the
map.getProjection?.()call in a try/catch insidegetProjection(),treating a throw the same as the already-handled
undefinedcase:We've worked around this locally with a
pnpm patchapplying exactly thischange to
@deck.gl/mapbox@9.3.9and would be happy to open a PR with it ifthat's useful.