React native mapbox custom UserLocation

On my development career I stoped having to implement a MapboxGL map into a React Native application. Fortunately someone had stood on that before and nicely released a package for It: React Native Mapbox

See full code at https://gist.github.com/nesjett/99388f73f1b4d82735bde89b90e24202

The problem

Everything was fine until we had to add some special behaviour to the UserLocation component (formerly <MapboxGL.UserLocation /> tag)

We had to add a circle around the user location, with a set radius that does not change It’s size on different zoom levels, reason why we were forced to abandon default components like <MapboxGL.SymbolLayer />

That was okay, we approached It by using the tag <MapboxGL.FillLayer> which was working and drawing the proper circle when placed directly inside the <MapboxGL.MapView> tag, but not working when added as children to the component UserLocation:

<MapboxGL.UserLocation visible showsUserHeadingIndicator>
       <MapboxGL.ShapeSource
            id="testShape" shape={interactionArea}
        >
            <MapboxGL.FillLayer
              id="circleArea"
              belowLayerID="pinsLayer"
              style={boundsStyle}
            />
        </MapboxGL.ShapeSource>
</MapboxGL.UserLocation>

Digging into that component for the react native mapbox library we found the following render block:

return (
      <Annotation
        animated={animated}
        id="mapboxUserLocation"
        onPress={onPress}
        coordinates={coordinates}
        style={{
          iconRotate: heading,
        }}
      >
        {children || normalIcon(showsUserHeadingIndicator, heading)}
      </Annotation>
    );

In fact, this means that any children that we add to the <MapboxGL.UserLocation> component was being wrapped inside an <Annotation> tag. So any tag that was not part of the “Annotation” component definition was not working as children.

The easiest approach was to create our own UserLocation component, which implements the same functionalities and dependencies, but where we have full ownership to modify It’s render method.

You can find the full file source for our custom <UserLocationCustom> component at https://gist.github.com/nesjett/99388f73f1b4d82735bde89b90e24202

We converted the original file to a TypeScript file (.tsx) as the original was plain JavaScript ( node_modules\@react-native-mapbox-gl\maps\javascript\components\UserLocation.js )

The relevant part for the implementation is that we added some methods to calculate the circle polygon inside this new component and then rendered the resulting feature as follows:

class UserLocationCustom extends React.Component<props, state> {
    ...

    return (
      <>
        <MapboxGL.ShapeSource
            id="asdfasdfasdf" shape={interactionArea}
        >
            <MapboxGL.FillLayer
              id="boundsFillaaaa"
              belowLayerID="pinsLayer"
              style={boundsStyle}
            />
        </MapboxGL.ShapeSource>
        <Annotation
          animated={animated}
          id="mapboxUserLocation"
          onPress={onPress}
          coordinates={coordinates}
          style={{
            iconRotate: heading,
          }}
        >
          {children || normalIcon(showsUserHeadingIndicator, heading)}
        </Annotation>
      </>
    );
  }
}

This way, we were able to draw our custom shape while still drawing the default icons for user location:

Note: If you are wondering where did we add the polygon calculation, It was on the native function _onLocationUpdate() on that same file. You can find the implementation in the gist posted above ( https://gist.github.com/nesjett/99388f73f1b4d82735bde89b90e24202 )

The official React Native MapboxGL repository is https://github.com/react-native-mapbox-gl/maps