Upgrade @maplibre/maplibre-react-native from 10 to 11

v11 is a breaking API rework (renamed components, restructured props,
event payloads on nativeEvent) and requires React Native's new
architecture. Doing it as a manual migration instead of taking
dependabot's #268 because the renames need code changes.

Changes in apps/mobile:

- RouteMap.tsx — switch to named imports; rename MapView→Map,
  ShapeSource→GeoJSONSource (with shape→data), LineLayer→Layer
  (with type="line" and paint instead of style), MarkerView→
  ViewAnnotation (with coordinate→lngLat); Camera uses
  initialViewState with bounds as [w,s,e,n] and a separate padding
  object; onLongPress reads lngLat from event.nativeEvent.

- app.config.ts — set `newArchEnabled: true`. MapLibre RN v11
  supports only the new architecture, so Fabric/TurboModules must
  be on for the Expo prebuild. Cast the config type since Expo SDK
  55's ExpoConfig typings don't yet include the field (the runtime
  does).

- package.json — `^10.4.2` → `^11.0.0`.

Supersedes dependabot #268; it'll auto-close once this merges.

Runtime verification requires an EAS dev build (native deps
changed + new arch flipped). JS-only surface typechecks + lints +
unit tests clean locally.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ullrich Schäfer 2026-04-19 12:09:06 +02:00
parent c3d9514f4d
commit 7e302b1fbe
No known key found for this signature in database
GPG key ID: A32FF691A0F752D9
4 changed files with 104 additions and 53 deletions

View file

@ -1,11 +1,16 @@
import { ExpoConfig, ConfigContext } from "expo/config";
export default ({ config }: ConfigContext): ExpoConfig => ({
// `newArchEnabled` isn't in the Expo SDK 55 typings yet but is an
// accepted runtime flag. MapLibre RN v11 requires the new architecture.
type ExpoConfigWithNewArch = ExpoConfig & { newArchEnabled?: boolean };
export default ({ config }: ConfigContext): ExpoConfigWithNewArch => ({
...config,
name: "trails.cool",
slug: "mobile",
version: "0.0.1",
scheme: "trailscool",
newArchEnabled: true,
orientation: "portrait",
icon: "./assets/icon.png",
userInterfaceStyle: "light",

View file

@ -1,6 +1,13 @@
import { useRef, useCallback } from "react";
import { StyleSheet, View, Text } from "react-native";
import MapLibreGL from "@maplibre/maplibre-react-native";
import {
Camera,
GeoJSONSource,
Layer,
Map,
ViewAnnotation,
type CameraRef,
} from "@maplibre/maplibre-react-native";
import type { Waypoint } from "@trails-cool/types";
import type { RouteSegment } from "./use-route-editor";
@ -40,7 +47,10 @@ export function RouteMap({
onWaypointDragEnd: _onWaypointDragEnd,
onWaypointPress,
}: RouteMapProps) {
if (!MapLibreGL) {
// `Map` is undefined when the native module isn't linked (e.g. running
// in Expo Go). Same safety net as before the v11 upgrade, just against
// a named import instead of the removed default export.
if (!Map) {
return (
<View style={styles.fallback}>
<Text style={styles.fallbackText}>Map</Text>
@ -64,15 +74,16 @@ function RouteMapInner({
onWaypointDragEnd: _onWaypointDragEnd,
onWaypointPress,
}: RouteMapProps) {
const ML = MapLibreGL!;
const cameraRef = useRef<MapLibreGL.CameraRef>(null);
const cameraRef = useRef<CameraRef>(null);
const handleLongPress = useCallback(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(event: any) => {
const coords = event?.geometry?.coordinates;
if (Array.isArray(coords) && coords.length >= 2) {
onLongPress(coords[1] as number, coords[0] as number);
// v11 event shape: payload lives on `event.nativeEvent.lngLat`
// (the old `event.geometry.coordinates` is gone).
const lngLat = event?.nativeEvent?.lngLat;
if (Array.isArray(lngLat) && lngLat.length >= 2) {
onLongPress(lngLat[1] as number, lngLat[0] as number);
}
},
[onLongPress],
@ -96,64 +107,59 @@ function RouteMapInner({
return (
<View style={styles.container}>
<ML.MapView
<Map
style={styles.map}
mapStyle={OSM_STYLE}
onLongPress={handleLongPress}
attributionEnabled={false}
logoEnabled={false}
attribution={false}
logo={false}
>
{bounds && (
<ML.Camera
<Camera
ref={cameraRef}
defaultSettings={{
bounds: {
ne: bounds.ne,
sw: bounds.sw,
paddingTop: 40,
paddingBottom: 40,
paddingLeft: 40,
paddingRight: 40,
},
initialViewState={{
bounds: [bounds.sw[0], bounds.sw[1], bounds.ne[0], bounds.ne[1]],
padding: { top: 40, right: 40, bottom: 40, left: 40 },
}}
/>
)}
{!bounds && (
<ML.Camera
defaultSettings={{
centerCoordinate: [10.0, 50.1],
zoomLevel: 6,
<Camera
initialViewState={{
center: [10.0, 50.1],
zoom: 6,
}}
/>
)}
{/* Route line */}
<ML.ShapeSource id="route" shape={routeGeojson}>
<ML.LineLayer
<GeoJSONSource id="route" data={routeGeojson}>
<Layer
id="route-line"
style={{
lineColor: "#2563eb",
lineWidth: 4,
lineOpacity: computing ? 0.4 : 1,
type="line"
paint={{
"line-color": "#2563eb",
"line-width": 4,
"line-opacity": computing ? 0.4 : 1,
}}
/>
</ML.ShapeSource>
</GeoJSONSource>
{/* Waypoint markers */}
{waypoints.map((wp, i) => (
<ML.MarkerView
<ViewAnnotation
key={`wp-${i}`}
coordinate={[wp.lon, wp.lat]}
lngLat={[wp.lon, wp.lat]}
>
<WaypointMarker
index={i}
isDayBreak={wp.isDayBreak}
onPress={() => onWaypointPress(i)}
/>
</ML.MarkerView>
</ViewAnnotation>
))}
</ML.MapView>
</Map>
{computing && (
<View style={styles.computingBanner}>

View file

@ -31,7 +31,7 @@
"dependencies": {
"@expo/metro-runtime": "^55.0.9",
"@gorhom/bottom-sheet": "^5.2.9",
"@maplibre/maplibre-react-native": "^10.4.2",
"@maplibre/maplibre-react-native": "^11.0.0",
"@sentry/cli": "^3.3.5",
"@sentry/react-native": "~7.11.0",
"@trails-cool/api": "workspace:*",

72
pnpm-lock.yaml generated
View file

@ -313,8 +313,8 @@ importers:
specifier: ^5.2.9
version: 5.2.9(@types/react@19.2.14)(react-native-gesture-handler@2.31.1(react-native@0.83.4(@babel/core@7.29.0)(@react-native/metro-config@0.85.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.5))(react@19.2.5))(react-native-reanimated@4.3.0(react-native-worklets@0.8.1(@babel/core@7.29.0)(@react-native/metro-config@0.85.1(@babel/core@7.29.0))(react-native@0.83.4(@babel/core@7.29.0)(@react-native/metro-config@0.85.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.5))(react@19.2.5))(react-native@0.83.4(@babel/core@7.29.0)(@react-native/metro-config@0.85.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.5))(react@19.2.5))(react-native@0.83.4(@babel/core@7.29.0)(@react-native/metro-config@0.85.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.5))(react@19.2.5)
'@maplibre/maplibre-react-native':
specifier: ^10.4.2
version: 10.4.2(@expo/config-plugins@55.0.8)(@types/geojson@7946.0.16)(@types/react@19.2.14)(react-native@0.83.4(@babel/core@7.29.0)(@react-native/metro-config@0.85.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.5))(react@19.2.5)
specifier: ^11.0.0
version: 11.0.0(@expo/config-plugins@55.0.8)(@types/geojson@7946.0.16)(@types/react@19.2.14)(react-native@0.83.4(@babel/core@7.29.0)(@react-native/metro-config@0.85.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.5))(react@19.2.5)
'@sentry/cli':
specifier: ^3.3.5
version: 3.3.5
@ -2099,14 +2099,25 @@ packages:
'@lezer/lr@1.4.8':
resolution: {integrity: sha512-bPWa0Pgx69ylNlMlPvBPryqeLYQjyJjqPx+Aupm5zydLIF3NE+6MMLT8Yi23Bd9cif9VS00aUebn+6fDIGBcDA==}
'@maplibre/maplibre-react-native@10.4.2':
resolution: {integrity: sha512-5qAfaEe66eMXyILklm2DMHwyaXwXxsZWVop4BqfU7AyTg13LHAcaMmLJNJ3jPkMtiJvjH2m8ywGnobdIg2I0lg==}
'@mapbox/jsonlint-lines-primitives@2.0.2':
resolution: {integrity: sha512-rY0o9A5ECsTQRVhv7tL/OyDpGAoUB4tTvLiW1DSzQGq4bvTPhNw1VpSNjDJc5GFZ2XuyOtSWSVN05qOtcD71qQ==}
engines: {node: '>= 0.6'}
'@mapbox/unitbezier@0.0.1':
resolution: {integrity: sha512-nMkuDXFv60aBr9soUG5q+GvZYL+2KZHVvsqFCzqnkGEf46U2fvmytHaEVc1/YZbiLn8X+eR3QzX1+dwDO1lxlw==}
'@maplibre/maplibre-gl-style-spec@24.8.1':
resolution: {integrity: sha512-zxa92qF96ZNojLxeAjnaRpjVCy+swoUNJvDhtpC90k7u5F0TMr4GmvNqMKvYrMoPB8d7gRSXbMG1hBbmgESIsw==}
hasBin: true
'@maplibre/maplibre-react-native@11.0.0':
resolution: {integrity: sha512-OCds0j9+1kKkx0B5DikMpYvrzCwYv3ti5j9K0eQadq/M0RjQ40CFUdVpi4N0DtZXZxThmnMI5huqArN6G9GmHw==}
peerDependencies:
'@expo/config-plugins': '>=7'
'@expo/config-plugins': '>=54.0.0'
'@types/geojson': ^7946.0.0
'@types/react': '>=16.6.1'
'@types/react': '>=19.1.0'
react: ^19.2.5
react-native: '>=0.59.9'
react-native: '>=0.80.0'
peerDependenciesMeta:
'@expo/config-plugins':
optional: true
@ -3794,6 +3805,7 @@ packages:
'@xmldom/xmldom@0.8.12':
resolution: {integrity: sha512-9k/gHF6n/pAi/9tqr3m3aqkuiNosYTurLLUtc7xQ9sxB/wm7WPygCv8GYa6mS0fLJEHhqMC1ATYhz++U/lRHqg==}
engines: {node: '>=10.0.0'}
deprecated: this version has critical issues, please update to the latest version
abab@2.0.6:
resolution: {integrity: sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==}
@ -4329,10 +4341,6 @@ packages:
dateformat@4.6.3:
resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==}
debounce@2.2.0:
resolution: {integrity: sha512-Xks6RUDLZFdz8LIdR6q0MTH44k7FikOmnh5xkSjMig6ch45afc8sjTjRQf3P6ax8dMgcQrYO/AR2RGWURrruqw==}
engines: {node: '>=18'}
debug@2.6.9:
resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
peerDependencies:
@ -5647,6 +5655,9 @@ packages:
json-stable-stringify-without-jsonify@1.0.1:
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
json-stringify-pretty-compact@4.0.0:
resolution: {integrity: sha512-3CNZ2DnrpByG9Nqj6Xo8vqbjT4F6N+tb4Gb28ESAZjYZ5yqvmc56J+/kuIwkaAMOyblTQhUW7PxMkUb8Q36N3Q==}
json5@2.2.3:
resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
engines: {node: '>=6'}
@ -6511,6 +6522,9 @@ packages:
quickselect@2.0.0:
resolution: {integrity: sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==}
quickselect@3.0.0:
resolution: {integrity: sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g==}
range-parser@1.2.1:
resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
engines: {node: '>= 0.6'}
@ -6781,6 +6795,9 @@ packages:
rtl-detect@1.1.2:
resolution: {integrity: sha512-PGMBq03+TTG/p/cRB7HCLKJ1MgDIi07+QU1faSjiYRfmY5UsAttV9Hs08jDAHVwcOwmVLcSJkpwyfXszVjWfIQ==}
rw@1.3.3:
resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==}
safe-buffer@5.1.2:
resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==}
@ -7110,6 +7127,9 @@ packages:
tinyqueue@2.0.3:
resolution: {integrity: sha512-ppJZNDuKGgxzkHihX8v9v9G5f+18gzaTfrukGrq6ueg0lmH4nqVnA2IPG0AEH3jKEk2GRJCUhDoqpoiw3PHLBA==}
tinyqueue@3.0.0:
resolution: {integrity: sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g==}
tinyrainbow@3.1.0:
resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==}
engines: {node: '>=14.0.0'}
@ -7728,7 +7748,7 @@ snapshots:
'@babel/types': 7.29.0
'@jridgewell/gen-mapping': 0.3.13
'@jridgewell/trace-mapping': 0.3.31
jsesc: 3.0.2
jsesc: 3.1.0
'@babel/helper-annotate-as-pure@7.27.3':
dependencies:
@ -9278,13 +9298,27 @@ snapshots:
dependencies:
'@lezer/common': 1.5.2
'@maplibre/maplibre-react-native@10.4.2(@expo/config-plugins@55.0.8)(@types/geojson@7946.0.16)(@types/react@19.2.14)(react-native@0.83.4(@babel/core@7.29.0)(@react-native/metro-config@0.85.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.5))(react@19.2.5)':
'@mapbox/jsonlint-lines-primitives@2.0.2': {}
'@mapbox/unitbezier@0.0.1': {}
'@maplibre/maplibre-gl-style-spec@24.8.1':
dependencies:
'@mapbox/jsonlint-lines-primitives': 2.0.2
'@mapbox/unitbezier': 0.0.1
json-stringify-pretty-compact: 4.0.0
minimist: 1.2.8
quickselect: 3.0.0
rw: 1.3.3
tinyqueue: 3.0.0
'@maplibre/maplibre-react-native@11.0.0(@expo/config-plugins@55.0.8)(@types/geojson@7946.0.16)(@types/react@19.2.14)(react-native@0.83.4(@babel/core@7.29.0)(@react-native/metro-config@0.85.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.5))(react@19.2.5)':
dependencies:
'@maplibre/maplibre-gl-style-spec': 24.8.1
'@turf/distance': 7.3.4
'@turf/helpers': 7.3.4
'@turf/length': 7.3.4
'@turf/nearest-point-on-line': 7.3.4
debounce: 2.2.0
react: 19.2.5
react-native: 0.83.4(@babel/core@7.29.0)(@react-native/metro-config@0.85.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.5)
optionalDependencies:
@ -11836,8 +11870,6 @@ snapshots:
dateformat@4.6.3: {}
debounce@2.2.0: {}
debug@2.6.9:
dependencies:
ms: 2.0.0
@ -13441,6 +13473,8 @@ snapshots:
json-stable-stringify-without-jsonify@1.0.1: {}
json-stringify-pretty-compact@4.0.0: {}
json5@2.2.3: {}
keyv@4.5.4:
@ -14584,6 +14618,8 @@ snapshots:
quickselect@2.0.0: {}
quickselect@3.0.0: {}
range-parser@1.2.1: {}
raw-body@2.5.3:
@ -14927,6 +14963,8 @@ snapshots:
rtl-detect@1.1.2: {}
rw@1.3.3: {}
safe-buffer@5.1.2: {}
safe-buffer@5.2.1: {}
@ -15231,6 +15269,8 @@ snapshots:
tinyqueue@2.0.3: {}
tinyqueue@3.0.0: {}
tinyrainbow@3.1.0: {}
tldts-core@7.0.28: {}