Skip to content

[Bug]: multiple async URI in <images> do not render on android #4062

@Kilowhisky

Description

@Kilowhisky

Mapbox Implementation

Mapbox

Mapbox Version

11.15.3

React Native Version

0.81.4

React Native Architecture

New Architecture (Fabric/TurboModules)

Platform

Android

@rnmapbox/maps version

10.2.5

Standalone component to reproduce

// BugReportExampleSymbols.js (pure JS)
import React, {useCallback, useMemo, useState} from 'react';
import {
  MapView,
  ShapeSource,
  SymbolLayer,
  Camera,
  Images,
} from '@rnmapbox/maps';
import {View, Button, StyleSheet} from 'react-native';

// If needed in your app entry, remember to set your token:
// import Mapbox from '@rnmapbox/maps';
// Mapbox.setAccessToken('YOUR_TOKEN');

const CENTER = [-74.00597, 40.71427];
const PIN_URL =
  'https://raw.githubusercontent.com/rnmapbox/maps/refs/heads/main/example/src/assets/pin.png';

const EMPTY_FC = {
  type: 'FeatureCollection',
  features: [],
};

const layerStyle = {
  iconImage: ['get', 'icon'], // read image id from feature properties.icon
  iconAllowOverlap: true,
  iconIgnorePlacement: true,
  iconSize: 0.7,
};

function makeRandomPoints(count, around) {
  const [lng, lat] = around;
  const features = [];

  for (let i = 0; i < count; i++) {
    // small jitter around center
    const dx = (Math.random() - 0.5) * 0.01;
    const dy = (Math.random() - 0.5) * 0.01;
    features.push({
      type: 'Feature',
      id: `pt-${i}`,
      properties: {
        icon: `pin-${i}`, // unique image id per point
      },
      geometry: {
        type: 'Point',
        coordinates: [lng + dx, lat + dy],
      },
    });
  }
  return {type: 'FeatureCollection', features};
}

export default function BugReportExampleSymbols() {
  const [fc, setFc] = useState(EMPTY_FC);
  const [imgs, setImgs] = useState({});

  const loadPointsAndImages = useCallback(() => {
    // 1) Prepare 10 unique image IDs (all backed by the SAME URL)
    const nextImgs = {};
    for (let i = 0; i < 10; i++) {
      // cache-bust query so RN treats them as distinct if needed
      nextImgs[`pin-${i}`] = {uri: `${PIN_URL}?v=${i}`};
    }
    setImgs(nextImgs);

    // 2) Create 10 point features that reference the above image ids
    setFc(makeRandomPoints(10, CENTER));
  }, []);

  const clearAll = useCallback(() => {
    setImgs({});
    setFc(EMPTY_FC);
  }, []);

  const camera = useMemo(
    () => <Camera centerCoordinate={CENTER} zoomLevel={14} />,
    [],
  );

  return (
    <View style={styles.container}>
      <View style={styles.row}>
        <Button title="Load 10 points + images" onPress={loadPointsAndImages} />
        <View style={styles.spacer} />
        <Button title="Clear" onPress={clearAll} />
      </View>

      <MapView style={styles.map}>
        {camera}

        {/* Provide images so SymbolLayer can resolve icon ids */}
        <Images images={imgs} />

        <ShapeSource id="bug-symbol-source" shape={fc}>
          <SymbolLayer id="bug-symbol-layer" style={layerStyle} />
        </ShapeSource>
      </MapView>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {flex: 1},
  row: {
    flexDirection: 'row',
    padding: 8,
    gap: 8,
    backgroundColor: '#ffffff',
    zIndex: 1,
  },
  spacer: {width: 8},
  map: {flex: 1},
});

Observed behavior and steps to reproduce

The images are resolved to a URI and set on the <images> component. But the images are never rendered to the screen or if they are only 1 or two is.

To reproduce, just run the code and then click the button to load the pins.

Expected behavior

It should render the pins on the map.

Notes / preliminary analysis

It appears to be a race condition of some sort because if i pause the code after react starts processing the set images it will render them all.

Additional links and references

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    bug 🪲Something isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions