Manspof
Manspof

Reputation: 357

React Native Maps call api request when onRegionChange

I'm using react native maps and I show business markers on the map. I want to call api to get new bossiness according the new coordinates.. what I try to do is call api on event 'onRegionChangeComplete', it works and show new markers on maps but I have some issues:

  1. maps is extremely slowly and take time to load the map( new streets and load the map)
  2. even I used debounce to call onRegionChangeComplete, it call api each time and it not looks good.
  3. when I move with map it take me to first location

the code

import React, { Component } from 'react'
import { View, Text, FlatList } from 'react-native'
import { inject, observer } from 'mobx-react/native'
import style from './style'
import I18n from '../../i18n'
import Icon from 'react-native-vector-icons/Feather'
import MapView, { PROVIDER_GOOGLE, Marker } from 'react-native-maps' // remove PROVIDER_GOOGLE import if not using Google Maps
import { BusinessDetailItem } from '../../components'
import { calcSize } from '../../utils'
import Colors from '../../utils/Colors'
import _ from 'lodash'

import MapView, { PROVIDER_GOOGLE, Marker } from 'react-native-maps' 

@inject('UserStore')
@observer
class BusinessMap extends Component {
  constructor(props) {
    super(props)
    this.state = {
      details: {},
      region: {
        latitude: props.UserStore.Latitude,
        longitude: props.UserStore.Longitude,
        latitudeDelta: Math.abs(props.UserStore.Latitude / 5000),
        longitudeDelta: Math.abs(props.UserStore.Longitude / 5000),
      },
    }

    this.debounce = _.debounce(data => this.onRegionChangeComplete(data), 1000)
  }

  componentDidMount() {}

  renderUserMarker = () => {
    const { UserStore } = this.props
    return <MapView.Marker tracksViewChanges={false} coordinate={{ latitude: UserStore.Latitude, longitude: UserStore.Longitude }} title={I18n.t('my_location')} />
  }

  renderBusinessMarkers = () => {
    const { UserStore } = this.props
    if (UserStore.orgs.length > 0) {
      return UserStore.orgs.map((info, i) => (
        <MapView.Marker
          tracksViewChanges={false}
          key={i}
          coordinate={{ latitude: info.location.coordinates[1], longitude: info.location.coordinates[0] }}
          title={info.org_name}
          onCalloutPress={() => {
            this.props.navigation.navigate('BusinessDetail', { org_id: info._id })
          }}
        />
      ))
    }
    return null
  }


  onRegionChangeComplete = region => {
    this.setState({ region })
    let query = {
      lat: region.latitude,
      lng: region.longitude,
      limit: 10,
    }
    await this.props.UserStore.getOrgsByLocation(query)

    console.log('onRegionChangeComplete', region)
  }
  renderMap = () => {
    console.log('this.state.region', this.state.region)
    const { UserStore } = this.props
    return (
      <View style={style.view_address_map}>
        <View style={style.view_map}>
          <MapView
            // scrollEnabled={false}
            showsUserLocation
            followUserLocation
            toolbarEnabled={false}
            showsIndoors={false}
            moveOnMarkerPress={false}
            style={style.map}
            region={this.state.region}
            onUserLocationChange={e => {
              'onUserLocation', console.log(e.nativeEvent)
            }}
            onPress={() => {
              console.log('onPres')
            }}
            onCalloutPress={e => {
              'onCalloutPress', console.log(e.nativeEvent)
            }}
            onRegionChange={this.onRegionChange}
            onRegionChangeComplete={this.onRegionChangeComplete}
          >
            {this.renderUserMarker()}
            {this.renderBusinessMarkers()}
          </MapView>
        </View>
      </View>
    )
  }
  renderBusinessDetailItem = (appointment, index) => {
    return <BusinessDetailItem {...appointment.item} navigation={this.props.navigation} addToFavouriteList={() => {}} />
  }

  renderBusinessList = () => {
    return (
      <View style={style.view_flat_last_minute_appointments}>
        <FlatList
          horizontal={true}
          disableVirtualization={true}
          contentContainerStyle={style.view_content_container_flat_list_last_minutes}
          data={this.props.UserStore.orgs}
          keyExtractor={(item, index) => `key-${index}`}
          renderItem={this.renderBusinessDetailItem}
          // ListEmptyComponent={this.renderEmptyComponent}
          showsHorizontalScrollIndicator={false}
          style={style.content_flat_list}
        />
      </View>
    )
  }

  render() {
    const { container, view_close_icon, icon_close } = style
    const { coordinates } = this.state.details
    const { UserStore } = this.props
    return (
      <View style={container}>
        <View style={view_close_icon}>
          <Icon
            name='x'
            size={calcSize(60)}
            color={Colors.black}
            style={icon_close}
            onPress={() => {
              this.props.navigation.goBack()
            }}
          />
        </View>
        {UserStore.orgs && this.renderMap()}
        {this.renderBusinessList()}
      </View>
    )
  }
}

export default BusinessMap

Upvotes: 4

Views: 2752

Answers (1)

Rachel Gallen
Rachel Gallen

Reputation: 28563

I'm not surprised your map is slow, given that the api is called so frequently/ every time you load a marker.

One viable solution [to reducing the api calls] would be to create a sort of "region cache" of the markers so that the ones previously loaded don't need to be queried again.

From Four tips to Optimize Your Map with React Native :

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:

Sample Code:

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

By caching the points already queried, then 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;
}

Another way of preventing unnecessary map renders would be to to implement shouldComponentUpdate in your custom marker class. Then, you have the control to specify that you want your marker to render only if its id is selected/not selected/anymore/zoomed etc.

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

If you have many markers that may not need to be shown, you might consider 'clustering', which 'groups' bunches of not-necessary-to-be-shown markers together into one large marker [as opposed to many small ones], but from the description of your app, I don't think this is particularly applicable to you.

Finally, I would say to review your code to ensure that your markers are not performing unnecessary calculations: If you can pass props as arguments, this will cut down on processing time, and improve performance!

Hope this helps

[by-the-by, I found this piece of code, that loads many markers, that I thought might interest you. But do consider the above points!]

Upvotes: 2

Related Questions