From f5f521496d6b14a71e816932040774619543c7d6 Mon Sep 17 00:00:00 2001 From: patrickangasmaki Date: Fri, 12 Jun 2026 10:57:17 +0300 Subject: [PATCH] Add guard for map styles in map operations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add isMapStyleReady before map layer/source operations to avoid “Style is not done loading” and "Cannot read properties of undefined" errors. Styles might be still loading even if map exists so checking just for the map is not enough --- .../FunctionalAreaVisualization.tsx | 3 ++- ui/src/utils/map/lineFromStopToInfraLink.ts | 8 ++++++-- ui/src/utils/map/mapUtils.ts | 15 +++++++++++++-- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/ui/src/components/map/stop-areas/FunctionalAreaVisualization/FunctionalAreaVisualization.tsx b/ui/src/components/map/stop-areas/FunctionalAreaVisualization/FunctionalAreaVisualization.tsx index e8e20f0d09..3e1f1622ae 100644 --- a/ui/src/components/map/stop-areas/FunctionalAreaVisualization/FunctionalAreaVisualization.tsx +++ b/ui/src/components/map/stop-areas/FunctionalAreaVisualization/FunctionalAreaVisualization.tsx @@ -4,6 +4,7 @@ import { FC, useEffect } from 'react'; import { MapInstance, useMap } from 'react-map-gl/maplibre'; import { useAppSelector } from '../../../../hooks'; import { selectSelectedStopAreaId } from '../../../../redux'; +import { isMapStyleReady } from '../../../../utils/map'; import { MapStop } from '../../types'; import { CanvasRenderer } from './CanvasRenderer'; import { pixelSize } from './constants'; @@ -24,7 +25,7 @@ function assertIsCanvasSource(source: unknown): asserts source is CanvasSource { // so we need to register the source and layer manually. function useMapSourceAndLayer(map: MapInstance | null) { useEffect(() => { - if (!map) { + if (!map || !isMapStyleReady(map)) { return noop(); } diff --git a/ui/src/utils/map/lineFromStopToInfraLink.ts b/ui/src/utils/map/lineFromStopToInfraLink.ts index 1608fa218e..9e01924d12 100644 --- a/ui/src/utils/map/lineFromStopToInfraLink.ts +++ b/ui/src/utils/map/lineFromStopToInfraLink.ts @@ -15,7 +15,11 @@ import { MapInstance } from 'react-map-gl/maplibre'; import { ReusableComponentsVehicleModeEnum } from '../../generated/graphql'; import { Point as JorePoint } from '../../types'; import { notNullish } from '../misc'; -import { createGeometryLineBetweenPoints, removeLayer } from './mapUtils'; +import { + createGeometryLineBetweenPoints, + isMapStyleReady, + removeLayer, +} from './mapUtils'; type LineAndDistance = { readonly line: LineString; @@ -117,7 +121,7 @@ export function addLineFromStopToInfraLink( map: MapInstance | undefined, geometry: Geometry, ) { - if (!map) { + if (!map || !isMapStyleReady(map)) { return; } diff --git a/ui/src/utils/map/mapUtils.ts b/ui/src/utils/map/mapUtils.ts index 746f116e6c..eb88fbcc94 100644 --- a/ui/src/utils/map/mapUtils.ts +++ b/ui/src/utils/map/mapUtils.ts @@ -17,8 +17,15 @@ const imageAssets: ReadonlyArray = [ }, ]; +/* + * Checks if the map style exists and that it has finished loading. This is needed before performing operations on the map, such as adding/manipulating layers or sources. + */ +export function isMapStyleReady(map: MapInstance) { + return !!map?.style && map.isStyleLoaded() === true; +} + export const removeLayer = (map: MapInstance, id: string) => { - if (map.getLayer(id)) { + if (isMapStyleReady(map) && map.getLayer(id)) { map.removeLayer(id); } }; @@ -68,7 +75,7 @@ export const getInteractiveLayerIds = (mapRef: RefObject) => { }; export const removeRoute = (map: MapInstance | undefined, id: string) => { - if (!map) { + if (!map || !isMapStyleReady(map)) { return; } if (map.getLayer(id)) { @@ -83,6 +90,10 @@ export const removeRoute = (map: MapInstance | undefined, id: string) => { }; export const addRoute = (map: MapInstance, id: string, geometry: Geometry) => { + if (!isMapStyleReady(map)) { + return; + } + // remove possible existing layers with same id removeRoute(map, id); map.addLayer({