maps icon indicating copy to clipboard operation
maps copied to clipboard

[Bug]: `onMapIdle` and `onRegionDidChange` events do not work

Open sabuhiteymurov opened this issue 8 months ago • 5 comments

Mapbox Implementation

Mapbox

Mapbox Version

10.1.38

React Native Version

0.76.9

Platform

iOS

@rnmapbox/maps version

default

Standalone component to reproduce

import React from 'react';
import { StyleSheet, View } from 'react-native';
import Mapbox, { MapView } from '@rnmapbox/maps';

const Map = () => {
  return (
    <View style={styles.container}>
      <MapView
        style={styles.map}
        onMapIdle={() => console.log('map idle')}
        onRegionDidChange={() => console.log('region did change')}
      />
    </View>
  );
};

export default Map;

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  map: {
    flex: 1,
  },
});

Observed behavior and steps to reproduce

  1. Launch the app using the component above.
  2. Let the map fully load.
  3. Try interacting with it by zooming or dragging.
  4. Notice that neither onMapIdle nor onRegionDidChange trigger initially.

I found that if any state change occurs after the map finishes loading (e.g., inside onDidFinishLoadingMap), then onRegionDidChange starts working as expected. onMapIdle still never triggers, regardless of state changes.

So there seems to be some internal dependency on state/context updates before events begin firing properly — and onMapIdle seems completely non-functional.

Expected behavior

onRegionDidChange should trigger whenever the map's visible region changes — either via user interaction or camera animation.

onMapIdle should fire once the map settles after a movement or animation.

Notes / preliminary analysis

No warnings or errors in the console.

Map loads and renders correctly otherwise.

I’ve temporarily added a dummy state update in the onDidFinishLoadingMap event to "kickstart" the event system, and this made onRegionDidChange work:

const [mapReady, setMapReady] = useState(false);

<MapView onDidFinishLoadingMap={() => {
  setMapReady(true); // Temporary workaround
}} />

This is not ideal, but confirms there might be an internal state issue blocking events from firing until something triggers a re-render.

Additional links and references

Minimal repro repo: https://github.com/sabuhiteymurov/rnmapbox-on-map-idle-reproducer

sabuhiteymurov avatar Apr 12 '25 13:04 sabuhiteymurov

I'm seeing this bug as well. It's very annoying.

ethandpowers avatar Apr 25 '25 02:04 ethandpowers

Same issue

alexanderoskin avatar May 06 '25 13:05 alexanderoskin

Same issue. I turned off the new architecture and its working.

smallzZz8 avatar May 07 '25 03:05 smallzZz8

same issue

Alvi24 avatar May 07 '25 20:05 Alvi24

We encountered this issue last year on iOS where onMapIdle would work initially, but would stop working after a while.

I wrote a workaround that listens for the touch-up event, then fetches the current map state manually. It preserves the current onMapIdle API structure so it just slots in. It doesn't handle imperative map movements so you'll need to add additional logic to handle those.

import { Position } from '@rnmapbox/maps/lib/typescript/src/types/Position';

const mapRef = useRef<MapView | null>(null);

const currentCameraPosition = useRef<GeoJSON.Point['coordinates'] | null>(null);

async function getMapState(center?: Position | null, zoom?: number | null, bounds?: [Position, Position] | null): Promise<MapboxGL.MapState | null> {
  if (!mapRef.current) return null;

  const [_center, _zoom, _bounds] = await Promise.all([
    center || mapRef.current.getCenter(),
    zoom || mapRef.current.getZoom(),
    bounds || mapRef.current.getVisibleBounds(),
  ]);

  return normalizeMapState(_center, _zoom, _bounds);
}

function normalizeMapState(center: Position, zoom: number, bounds: [Position, Position]): MapboxGL.MapState {
  return {
    properties: {
      center,
      bounds: { ne: bounds[0], sw: bounds[1] },
      zoom,
      heading: -1,
      pitch: -1,
    },
    gestures: { isGestureActive: true },
    timestamp: Date.now(),
  };
}

return (
  <MapView
    ref={mapRef}
    onTouchEnd={async () => {
      const mapState = await getMapState();
  
      if (mapState && !_.isEqual(currentCameraPosition.current, mapState.properties.center) {
        currentCameraPosition.current = mapState!.properties.center;
        onMapMove?.(mapState!);
      }
    }}
  >
    // map components
  </MapView>
);

W1MMER avatar May 14 '25 02:05 W1MMER

Please fix this problem!!! We can not really use the library without having event listeners in place!

jannis6023 avatar Jul 29 '25 15:07 jannis6023

Can you give https://www.npmjs.com/package/@rnmapbox/maps/v/10.1.43-rc.0 a try? It fixes a race condition on iOS, mostly affecting new architecture

mfazekas avatar Sep 04 '25 14:09 mfazekas

Still seeing this issue with:

  • @rnmapbox/maps: 10.1.43
    • RNMapboxMapsVersion: 11.14.4
    • or default v10
  • newArchEnabled: true
  • react-native: 0.76.9
  • expo: 52.0.47

mgray88 avatar Sep 11 '25 16:09 mgray88

@mgray88 you’ve tried the simple component in the report?!

mfazekas avatar Sep 11 '25 17:09 mfazekas

I have managed to upgrade to Expo 53 in the meantime, so the new environment is:

  • @rnmapbox/maps: 10.1.43
    • RNMapboxMapsVersion: 11.14.4
    • or default v10
  • newArchEnabled: true
  • react-native: 0.79.6
  • expo: 53.0.22

Since the MapView component in the report had no children, I started with that and yes I could receive both onMapIdle and onRegionDidChange events (though the onRegionDidChange events were delayed by a full second). Adding back in components I needed, it turns out the LocationPuck for me caused onMapIdle and onRegionDidChange to no longer be called.

ETA: It seems to be specifically the pulsing property that prevents the events from being called

mgray88 avatar Sep 11 '25 18:09 mgray88

@mfazekas This bugs is back in 10.1.44 :(

flessard avatar Sep 17 '25 00:09 flessard

@flessard have you tried the component in this bug report? Can you be as specific as possible?!

mfazekas avatar Sep 17 '25 10:09 mfazekas

@mfazekas I’m trying to understand. The problem isn’t constant; it depends on where I am on the map. Sometimes the event is triggered, sometimes not. I made a short video showing the issue. In my video, when I first enter the map, onMapIdle is not triggered. If I move the map, it still doesn’t get triggered. If I zoom out, then onMapIdle eventually gets triggered. But if I zoom in, onMapIdle stops being triggered. I don’t know why. Do you have any idea? bug movie

flessard avatar Sep 18 '25 00:09 flessard