[fa icon="calendar"] Publié le 29 October 2018 par Arnaud Augustin


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.

 

More markers, too much computation

 

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 onerous in terms of performance and UX due to heavy computations when rendering a high number of markers.

1 - Limit the computations made by each marker

 

Passing a prop to each marker is more efficient than handling calculations in every individual one.

 

Example

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 MapView component and then pass a prop isMapZoomed to each marker so that it can behave accordingly.

 

render() {
    return (
        <MapView>
            {this.state.pois && this.state.pois.map(marker => (
                <CustomMarker
                    key={marker.id}
                    isZoomed={this.isMapZoomed(this.state.displayedRegion)}
                />
            ))}
        </MapView>
    );
}

 Make sure that each marker is not doing computation that can be mutualized in the parent component instead. Less computation means a better performance!

 

2 - Render a marker only when it is useful

 

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.

 

Example

You want to display a Marker  in a different way when it is selected and when it is not. At first sight, implementing a MapView displaying a long list of markers thanks to a map() function called upon them should be enough. When clicking on a marker, the onPress  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 selectedId) will change, the render 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.

 

long_render_RN_maps

 

✅ : A way of preventing unnecessary renders of the map component is to implement shouldComponentUpdate in your custom marker class. Thus, you can specify that you want your marker to render only if its id was selected and is not selected anymore or the opposite for example.

 

shouldComponentUpdate(prevProps) {
    return prevProps.isSelected !== this.props.isSelected ||
           prevProps.isZoomed !== this.props.isZoomed
}
 

The flame chart obtained thus becomes:

 

quick_render_RN_maps

 

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.

 

3 - Optimize the number of calls to the API when fetching the markers

 

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.

 

Example

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:

 

map_marker_component_hierarchy
 
 

❌: 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:

 
const key = createKey({pinTypes, searchText});
this.setState({
    queriedRegion: {
        [key]: boundaries(region)
    },
    pointsOfInterest,
})

 

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).

 

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;
}

 

4 - Only display the needed data

 

This is particularly true for a map. Displaying unnecessary data can overcharge your map which in turn negatively impacts the mobile UX.

 

Example

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:

 

  1. yarn add @bamtech/react-native-component-map-clustering
  2. Import <MapView> from it instead of react-native-maps.
  3. Pass a prop clustering (true) to your MapView
  4. Enjoy!

 

Here are the results obtained :
 
Without clustering
With clustering
laggy_map smooth_map
 
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 ;)

Liked this article? Interested in building an app with us?

Contact a React Native expert in Paris




Want to rate this article?
Submit Rating

Topics: Mobile UX, React Native, maps, Performance, optimization