In this article, you will find tips to improve the performance of your map with React Native in order to avoid slow animations, lags and to enhance the UX.
When implementing a map with a lot of markers, optimization is a critical point to avoid bad performance. During one of my projects, we had to display car agencies all over France on a map with React Native. With only 50 pins, the map worked fine. Yet, when increasing the number of pins from 50 to 1500, the app became (very) laggy and, let's admit it, unusable.
Some of the tips you will find here are not specific to react-native-maps but are general optimization tips. However, not paying attention to them can be very onerousin terms of performance and UX due to heavy computations when rendering a high number of markers.
Passing a prop to each marker is more efficient than handling calculations in every individual one.
Take the case in which you want to render a map with markers behaving differently when the map is zoomed in and when it is not.
? : A bad way of doing it is to pass the region displayed by the map to each marker and make the check about the zoom inside each marker. Thus, each one will have to recompute the check about the zoom when rendering so as to know how it is supposed to be displayed.
? : The right way to do so is to check if the map is zoomed into the ++code>MapView++/code> component and then pass a prop ++code>isMapZoomed++/code> to each marker so that it can behave accordingly.
++pre>++code>render() {
return (
<MapView>
{this.state.pois && this.state.pois.map(marker => (
<CustomMarker
key={marker.id}
isZoomed={this.isMapZoomed(this.state.displayedRegion)}
/>
))}
</MapView>
);
}++/code>++/pre>
Make sure that each marker is not doing computation that can be mutualized in the parent component instead. Less computation means a better performance!
You have to make sure the markers (and the map) are not rendered too often (eg. when tapping on a marker, moving the map, etc.) but only when needed. This will lead to unnecessary computation and performance loss.
You want to display a ++code>Marker++/code> in a different way when it is selected and when it is not. At first sight, implementing a ++code>MapView++/code> displaying a long list of markers thanks to a ++code>map()++/code> function called upon them should be enough. When clicking on a marker, the ++code>onPress++/code> callback will trigger a modification of the state by storing the selected marker id.
? : If you implement your map this way, when the map state (more precisely ++code>selectedId++/code>) will change, the ++code>render++/code> function of your map will be called and each marker will be rendered.
Note : in order to compare the difference of performance between the two implementations, we will use a tool called the 'flame chart'. It allows us to examine the JS thread on a chronological axis. You can access it in Chrome by connecting your application to the Chrome debugger and ?? + I ? Performance. Then, typing ? + E will start to record your component lifecycles. This tool is a good first step to investigate about UX and performance trouble.
Here is the flame chart obtained when selecting a marker among 500 markers. The amount of time between the click and the end of the last render is around 225ms.
? : A way of preventing unnecessary renders of the map component is to implement ++code>shouldComponentUpdate++/code> in your custom marker class. Thus, you can specify that you want your marker to render only if its ++code>id++/code> was selected and is not selected anymore or the opposite for example.
++pre>++code class="language-javascript">shouldComponentUpdate(prevProps) {
return prevProps.isSelected !== this.props.isSelected ||
prevProps.isZoomed !== this.props.isZoomed
}++/code>++/pre>
The flame chart obtained thus becomes:
The main information we can get on this chart is that only one marker is updated when clicking on it. This way, the time between the click on the marker and the end of all the renders will be is reduced from 225ms to about 65ms. In terms of UX, this means that the application will be more responsive.
Optimizing the number of calls you make is very beneficial and will improve the performance of your application. Moreover, displaying cached markers instead of refetching them will enhance the UX by decreasing the user waiting time.
Let's imagine you want to display a lot of markers filtered according to their type and a search text on the map. In order to give you a clearer view of the code, here is a schematic representing the hierarchy of the map and marker components:
?: Calling every points each time you want to display a new part of the map is very greedy in terms of resources.
? : An efficient way to reduce the amount of calls you need to make is to create a local cache. For example, before each request, create a key with the parameters (type of the poi, description, etc.) of your request and store it in the state alongside the queried region. It can be done like so:
++pre>++code>const key = createKey({pinTypes, searchText});
this.setState({
queriedRegion: {
[key]: boundaries(region)
},
pointsOfInterest,
})++/code>++/pre>
Thus, before fetching new points, you can check if the last call was done with the same key and with a region wrapping the current one. If so, you know you already have all the points you wanted to display and that you can safely ignore this request (considering you always query all the points within the region that match the criterion and you don't filter depending on the zoom level).
++pre>++code>if(
this.state.queriedRegion &&
this.state.queriedRegion[key] &&
isRegionWithin(boundaries(region), this.state.queriedRegion[key])
) {
//Do not query the points, they are already fetched !
return;
}++/code>++/pre>
This is particularly true for a map. Displaying unnecessary data can overcharge your map which in turn negatively impacts the mobile UX.
Do you need to display all the the markers or do you need the user to know there are 30 points of interests around this area? Asking this kind of question will simplify your life both in terms of performance and UX. Let your application be the lightest possible!
?: Displaying all the points is a bad decision if you have too many, since it can prevent the user from distinguishing the points and selecting them separately. It will also slow down the map.
?: Since, it is useless to display all the markers, the most intuitive solution is to use clustering. For example, you could use @bamtech/react-native-component-map-clustering. If your application is already running with react-native-maps, then, all you need to do is:
Here are the results obtained :
When zooming out with clustering, the map stays light and you can still have a clear overview of what is going on instead of pins overlapping each other and hiding the map. Since performance and UX are deeply linked, you should always think about the two when working on your map. The pins whether clustered or not are also customizable! PRs welcome ;)