Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bug]: Freeze when camera moves with many markers on all Mapbox version greater than 10.18.2 #3764

Open
mcastets opened this issue Jan 31, 2025 · 0 comments
Labels
bug 🪲 Something isn't working

Comments

@mcastets
Copy link

mcastets commented Jan 31, 2025

Mapbox Implementation

Mapbox

Mapbox Version

10.19.2

React Native Version

0.76

Platform

iOS

@rnmapbox/maps version

10.1.33

Standalone component to reproduce

import React from 'react';
import { useEffect, useMemo, useRef } from 'react';
import Mapbox, { Camera, MapView } from '@rnmapbox/maps';
import { AppNavigationProp, GeoPosition, MapMarker, PlaceDetails } from '@/types';
import {
    FRANCE_CENTER_COORDINATES,
    FRANCE_ZOOM_LEVEL,
    INITIAL_CENTER_COORDINATES,
    INITIAL_ZOOM_LEVEL,
    mapboxPlaceBboxToZoomLevel,
    POI_POSITION_ZOOM_LEVEL,
} from '@/modules/map/utils/map';
import { Markers } from '@/modules/map/components/markers/Markers';
// eslint-disable-next-line import/no-unresolved -- Android and iOS specific components
import { UseMarkersState } from '@/modules/map/hooks/useMarkers';
import { useMarkerStatus } from '@/modules/map/hooks/useMarkerStatus';
import { noop } from '@/common/utils/helpers';
import { envAsString } from '@/common/utils/envs';
import { StyleSheet, View } from 'react-native';

type MapWithMarkersProps = UseMarkersState & {
    navigation: AppNavigationProp;
    selectedMarker: MapMarker | null;
    onMarkerPress: (marker: MapMarker) => void;
    selectedPlaceDetails?: PlaceDetails;
    favoriteMarkers: MapMarker[] | undefined;
    favoriteFilterActive: boolean;
    onFavoritePress: () => void;
};

const MapWithMarkers = ({ selectedPlaceDetails, ...useMarkersState }: MapWithMarkersProps) => {
    const { markers, updateBoundsAndRefetchMarkers } = useMarkersState;
    const { displayedStatus, smallStatus } = useMarkerStatus();

    // Use a stable reference for the markers to avoid unnecessary re-renders
    const markersDisplayed = useMemo(() => markers || [], [markers]);

    const cameraRef = useRef<Camera>(null);

    const handleOnMapLoaded = () => {
        if (cameraRef.current) {
            // Set the camera to France
            setCameraPosition(FRANCE_CENTER_COORDINATES, FRANCE_ZOOM_LEVEL);
        }
    };

    // Refetch markers when the map is idle
    const handleOnMapIdle = (state: Mapbox.MapState) => {
        updateBoundsAndRefetchMarkers(state.properties.bounds);
    };

    const setCameraPosition = (center: GeoPosition, zoomLevel: number) => {
        cameraRef.current?.setCamera({
            centerCoordinate: center,
            zoomLevel,
            animationMode: 'flyTo',
            animationDuration: 3000,
        });
    };

    // Set the camera to the selected place when it changes
    useEffect(() => {
        if (selectedPlaceDetails?.position) {
            // Convert the bbox to a zoom level if it exists
            const zoomLevel = selectedPlaceDetails.bbox
                ? mapboxPlaceBboxToZoomLevel(selectedPlaceDetails.bbox)
                : POI_POSITION_ZOOM_LEVEL;
            setCameraPosition(selectedPlaceDetails.position, zoomLevel);
        }
    }, [selectedPlaceDetails]);

    return (
        <View style={[styles.container, styles.size]}>
            <MapView
                style={styles.size}
                onDidFinishLoadingMap={handleOnMapLoaded}
                onMapIdle={handleOnMapIdle}
                projection="globe"
                rotateEnabled={false}
                scaleBarEnabled={false}
                pitchEnabled={false}
                styleURL={''}
                localizeLabels={{ locale: 'current' }}
            >
                <Camera
                    ref={cameraRef}
                    defaultSettings={{
                        centerCoordinate: INITIAL_CENTER_COORDINATES,
                        zoomLevel: INITIAL_ZOOM_LEVEL,
                    }}
                />
                <Markers
                    markers={markersDisplayed}
                    filteredStatus={displayedStatus}
                    smallStatus={smallStatus}
                    onMarkerPress={noop}
                />
            </MapView>
        </View>
    );
};

const styles = StyleSheet.create({
    size: {
        width: '100%',
        height: '100%',
    },
    container: {
        position: 'relative',
        flex: 1,
    },
});

export default MapWithMarkers;
import React from 'react';
import { memo, useMemo } from 'react';
import Mapbox from '@rnmapbox/maps';
import { MapMarker, MapMarkerStatus } from '@/types';
import { GeoJSON } from 'geojson';
import { images } from '@/assets';
import { noop } from '@/common/utils/helpers';

type MarkersProps = {
    markers: MapMarker[];
    onMarkerPress: (marker: MapMarker) => void;
    filteredStatus: MapMarkerStatus[];
    smallStatus: MapMarkerStatus[];
    userFavoriteChargingLocationIds?: string[];
};

export const Markers = memo(({ markers, filteredStatus, smallStatus }: MarkersProps) => {
    const geoJson = useMemo<GeoJSON>(
        () => ({
            type: 'FeatureCollection',
            features: markers
                .filter((marker) => filteredStatus.includes(marker.status))
                .map((marker) => {
                    return {
                        type: 'Feature',
                        geometry: {
                            type: 'Point',
                            coordinates: marker.pos,
                        },
                        properties: {
                            id: marker.id,
                            icon: 'available',
                            symbolSortKey: 5,
                        },
                    };
                }),
        }),
        [markers, filteredStatus, smallStatus],
    );

    return (
        <>
            <Mapbox.Images
                images={{
                    available: images.pinAvailable,
                }}
            />
            <Mapbox.ShapeSource
                id="mapMarkersShapeSource"
                shape={geoJson}
                onPress={noop}
                hitbox={{ width: 30, height: 30 }}
            >
                <Mapbox.SymbolLayer
                    id="mapMarkersSymbolLayer"
                    style={{
                        iconImage: ['get', 'icon'],
                        iconSize: 0.3,
                        iconAllowOverlap: true,
                        symbolSortKey: ['get', 'symbolSortKey'],
                    }}
                />
            </Mapbox.ShapeSource>
        </>
    );
});

Observed behavior and steps to reproduce

Regression when camera flies to / moves and there are many markers: app is freezing for a few seconds when RNMapboxMapsVersion is greater than 10.18.2

Mapbox 10.18.2

mapbox-ios-sim-10.18.2.mov

Mapbox 10.19.2

mapbox-ios-sim-10.19.2.mov

Expected behavior

No freeze though some sporadic and quick flickering (as in 10.18.2) is acceptable when high number of markers is displayed.

Notes / preliminary analysis

I have tested many combinations of version with @rnmapbox/maps and RNMapboxMapsVersion, device and simulator, the conclusion is always the same: any version greater than 10.18.2 shows some freezes issue, on iOS, when dealing with high number of markers.

No freeze Device RN @rnmapbox/maps RNMapboxMapsVersion
Simulator iPhone SE 3rd gen 17.2 0.75 10.1.29 default (10.18.2)
Simulator iPhone SE 3rd gen 17.2 0.76 10.1.29 default (10.18.2)
Simulator iPhone SE 3rd gen 17.2 0.76 10.1.33 default (10.19.0)
Simulator iPhone SE 3rd gen 17.2 0.76 10.1.33 10.18.2
Build error Simulator iPhone SE 3rd gen 17.2 0.76 10.1.33 11.4.0
Simulator iPhone SE 3rd gen 17.2 0.76 10.1.33 11.8.0
Device iPhone 12 mini 18.1.1 0.76 latest commit on main 10.18.2
Simulator iPhone SE 3rd gen 17.2 0.76 latest commit on main 10.19.2
Simulator iPhone SE 3rd gen 17.2 0.76 latest commit on main 11.8.0
Device iPhone 12 mini 18.1.1 0.76 latest commit on main 11.8.0
Simulator iPhone SE 3rd gen 17.2 0.76 latest commit on main 11.9.1

Should I post an issue to the https://github.com/mapbox/mapbox-maps-ios repo? I'm not comfortable with Swift nor Xcode.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug 🪲 Something isn't working
Projects
None yet
Development

No branches or pull requests

1 participant