[New Architecture][iOS] Changing a Polygon's zIndex makes the overlay disappear (Fabric reorder detaches the GMSPolygon)

Author: ThomasPonzoVismaCreated Jun 8, 2026Updated Sep 7, 2026
Labelsstale

Summary

On Fabric / iOS with provider="google", changing an overlay's zIndex causes that overlay (and sometimes others) to vanish from the map. When two overlays change zIndex in the same commit, the demoted one can also keep a stale fillColor (its prop update is dropped).

Environment

  • react-native-maps: 1.27.2
  • Platform: iOS, provider="google"
  • New Architecture (Fabric): enabled
  • React Native 0.83.x / Expo SDK 55

Reproduction

Render two overlapping polygons; on press, raise the pressed one's zIndex:

javascript
<MapView provider="google" style={{ flex: 1 }} initialRegion={/* ... */}>
  <Polygon coordinates={triA} fillColor="rgba(255,0,0,0.4)"  zIndex={selected === 'a' ? 2 : 1} onPress={() => setSelected('a')} tappable />
  <Polygon coordinates={triB} fillColor="rgba(0,0,255,0.4)" zIndex={selected === 'b' ? 2 : 1} onPress={() => setSelected('b')} tappable />
</MapView>

Tapping a polygon (which changes its zIndex) makes it disappear; tapping the other can leave the first stuck on its previous fill color.

Root cause

A zIndex change makes Fabric reorder the map's overlay children, which it does by unmounting and re-mounting the same view. react-native-maps treats that unmount as a removal: RNMapsGooglePolygonView didRemoveFromMap detaches the GMSPolygon (_view.map = nil). The immediate re-mount doesn't reliably re-attach, and a pure reorder doesn't re-deliver props — so the overlay stays detached. When two overlays reorder in one transaction, the demoted overlay's prop update (e.g. fillColor) is also lost.

The GMS draw order is already controlled by GMSPolygon.zIndex (set in updateProps), so a reorder should not need to detach/re-attach the overlay at all.

Suggested fix

Make a reorder a no-op for the GMS attachment: defer the detach by one runloop tick and cancel it if the same view is re-mounted in the same (synchronous) transaction. Only a genuine removal (no matching re-mount) detaches:

objc
// RNMapsGooglePolygonView.mm  (add BOOL _detachScheduled;)
- (void)didInsertInMap:(AIRGoogleMap *)map {
    _detachScheduled = NO;          // cancel a pending detach → this is a reorder, not a removal
    _pendingMap = map;
    [self attachIfReady];
}

- (void)didRemoveFromMap {
    _detachScheduled = YES;
    __weak RNMapsGooglePolygonView *weakSelf = self;
    dispatch_async(dispatch_get_main_queue(), ^{
        RNMapsGooglePolygonView *s = weakSelf;
        if (s == nil || !s->_detachScheduled) return;   // re-mounted → keep attached
        s->_detachScheduled = NO;
        s->_view.map = nil;
    });
}

(prepareForRecycle still does the real teardown for genuine removals and resets the flag.)

A working patch-package patch for 1.27.2 is available; PR can follow.

Source: react-native-maps/react-native-maps