react-native-maps: Custom marker image very slow performance
Is this a bug report?
Yes
Have you read the Installation Instructions?
Yes
Environment
"dependencies": {
"expo": "^25.0.0",
"react": "16.2.0",
"react-native": "https://github.com/expo/react-native/archive/sdk-25.0.0.tar.gz"
}
Steps to Reproduce
Markers with default Google Maps marker image show up very quickly, no issues at all.
But when I use a custom image (size is 1kb) it struggles to render.
Expected Behavior
For markers to show up quickly.
Actual Behavior
Markers take too long to show up. For about 200+ markers, it takes around 10-20 seconds.
Reproducible Demo
App.js
import React from 'react';
import { Dimensions } from 'react-native';
import { MapView } from 'expo';
import Marker from './Marker';
import haversine from 'haversine';
const { width, height } = Dimensions.get('window');
const ASPECT_RATIO = width / height;
const LATITUDE = -37.812365;
const LONGITUDE = 144.962338;
const LATITUDE_DELTA = 0.0222;
const LONGITUDE_DELTA = LATITUDE_DELTA * ASPECT_RATIO;
export default class App extends React.Component {
constructor() {
super();
this.state = {
region: {
latitude: LATITUDE,
longitude: LONGITUDE,
latitudeDelta: LATITUDE_DELTA,
longitudeDelta: LONGITUDE_DELTA
},
data: [],
isLoaded: false,
boundingBox: {
westLng: 0,
southLat: 0,
eastLng: 0,
northLat: 0
}
}
}
componentDidMount() {
this._mounted = true;
}
componentWillUnmount() {
this._mounted = false;
}
getData = async (region) => {
const centre = {
latitude: region.latitude,
longitude: region.longitude
}
const screenVerticalBoundary = {
latitude: this.state.boundingBox.northLat,
longitude: region.longitude
}
const distance = haversine(centre, screenVerticalBoundary, {unit: 'meter'});
const url = `http://test.com`;
let data = await(await fetch(url)).json();
this.setState({data: data, isLoaded: true});
}
onRegionChangeComplete = (region) => {
let boundingBox = this.getBoundingBox(region);
this.setState({boundingBox});
this.getData(region);
}
getBoundingBox = (region) => {
let boundingBox = {
westLng: region.longitude - region.longitudeDelta/2, // westLng - min lng
southLat: region.latitude - region.latitudeDelta/2, // southLat - min lat
eastLng: region.longitude + region.longitudeDelta/2, // eastLng - max lng
northLat: region.latitude + region.latitudeDelta/2 // northLat - max lat
}
return boundingBox;
}
isInBoudingBox(coordinate) {
if (coordinate.latitude > this.state.boundingBox.southLat && coordinate.latitude < this.state.boundingBox.northLat &&
coordinate.longitude > this.state.boundingBox.westLng && coordinate.longitude < this.state.boundingBox.eastLng)
{
return true;
}
return false;
}
render() {
return (
<MapView style={ { flex: 1 } } initialRegion={ this.state.region } onRegionChangeComplete={this.onRegionChangeComplete}>
{this.state.isLoaded ? this.state.data.map(p =>
this.isInBoudingBox({ latitude: parseFloat(p.lat), longitude: parseFloat(p.lon) }) ?
<Marker key={p.st_marker_id} coordinate={ { latitude: parseFloat(p.lat), longitude: parseFloat(p.lon) } } callout="This is a test" />
: null)
: console.log('data does not exist yet')}
</MapView>
);
}
}
Marker.js
import React from 'react';
import { MapView } from 'expo';
import { View, Text } from 'react-native';
import markerImage from './assets/marker-128.png';
export default class Marker extends React.Component {
constructor(props) {
super(props);
this.state = {
tracksViewChanges: true
}
}
componentWillReceiveProps(nextProps) {
if (this.props !== nextProps) {
this.setState(() => ({
tracksViewChanges: true,
}));
}
}
componentDidUpdate() {
if (this.state.tracksViewChanges) {
this.setState({
tracksViewChanges: false
});
}
}
render() {
return (
<MapView.Marker coordinate={ this.props.coordinate } tracksViewChanges={this.state.tracksViewChanges} image={markerImage}>
<MapView.Callout>
<View>
<Text>{this.props.callout}</Text>
</View>
</MapView.Callout>
</MapView.Marker>
)
}
}
I found a few proposed solutions in the repo (adding key, the tracksViewChanges trick, getting rid of funny this.setstate, etc )but none really made any difference.
I am considering using clustering as suggested elsewhere but I would like to try to resolve the issue without having to use it.
Can anyone help please?
About this issue
- Original URL
- State: closed
- Created 6 years ago
- Comments: 22 (3 by maintainers)
@aeskafi It’s not a bug. It’s for updating the marker live, to mimick how it works on iOS. You do not have to comment out code. All you have to do is set
tracksViewChanges={false}. If you are loading an image inside the marker, you could start withtracksViewChanges={true}, listen to the load even on your image, and then change it tofalse.Make sure to set on the <Marker/> the tracksViewChanges prop to false once all Markers are rendered. For some reason on iOS if you provide a custom marker (Image, View, Svg etc…) it keeps re-rendering it causing the UI thread to go nuts forever.
For me, following this instructions from @heysailor, solved it:
You can find the original quote on the commit he made.
This comment from @davidhellsing was pretty helpful too, and points on the same direction.
@danielgindi huge thanks, I got stuck on this for a while.
I had around 100 markers, that would sometimes completely, partially, or not load (at random). This was on iOS (simulator) with
tracksViewChanges={false}.Upon following your last comment, and creating a custom marker, I’m no longer having this issue, and performance is fixed.
Not sure if this is loosely related to some issues in #578?
This is working for me, in case anyone else finds it of use:
@limpygnome If you only want a custom image for your marker you probably can fix the performance issues now using the icon property in the marker component which was added in v0.23.0
Did not help me though since my markers also contain badges. My solution was the following
The timeout however is not very pretty and probably dangerous to use here. But this helped to prevent markers to be initially rendered partially (sometimes). Any ideas why this could happen - without the timeout?
@lucianoacsilva I’d be something like the code below with hooks. Or if you’re using classes then i think you put something of this sort in
componentWillUnmount. The point is just store thetimerIdsomewhere and callclearTimeouton it when un-mounting.