I am very new to learning React Native and would appreciate any help that can be provided with my code. I am currently working off of tutorials and have been stuck for hours. Hopefully this is enough info to get some help. Thanks in advance!
My goal
I am trying to do three things (in this order):
- Get the user's current location
- Fetch the items data from an API (example outlined below). It should be noted that eventually the contents of the fetch will be dependent on the user's current location.
- Parse the items data to create markers and a unique list of categories and place those on the map with the user's current location at center to begin with.
I am looking to be able to watch the user's location and move and update the map accordingly. I also don't want to show the map until all of the markers are in place.
Here are my react versions: react-native-cli: 2.0.1 react-native: 0.63.2
My Bugs
I am using both the Android Studio emulator as well as the Xcode emulator and am currently running into the following problems:
- The iOS emulator on Xcode renders fine the first time, but on subsequent refreshes I see 1 or two of my 5 markers missing.
- On Android, the map loads perfectly then appears to immediately redraw itself and center on the Googleplex in California instead of the user's current location.Emulator location is set to London, UK
I suspect I might have some race condition with fetching the items and current location before the map renders but I can't be sure.
My Code
//my API response structure
{"id": "96845","title": "Item_title_goes_here","image": "https://someURL/image.JPG","stories": 46,"lat": some_lat_number,"lon": some_lon_number,"category": "category_name","description": "long_description"},
//ajax.js export default { async fetchInitialItems() { try { const response = await fetch(apiHost +'/api/v1/items/'); const responseJson = await response.json(); return responseJson; } catch (error) { console.error(error); } }, async fetchItemDetail(itemId) { try { const response = await fetch(apiHost +'/api/items/'+ itemId); const responseJson = await response.json(); return responseJson; } catch (error) { console.error(error); } },};
//ExploreScreen.js (my map component)import React, { Component } from 'react';import { View, Text, StyleSheet, Image, Animated, Dimensions, TouchableOpacity, PermissionsAndroid, ScrollView, Platform, StatusBar,} from 'react-native';import MapView, { PROVIDER_GOOGLE, Marker, Callout, Polygon,} from 'react-native-maps';import PropTypes from 'prop-types';import Geolocation from '@react-native-community/geolocation';import { mapDarkStyle, mapStandardStyle } from '../model/mapData';import ajax from '../utils/ajax';import MapCarousel from './MapCarousel';import Ionicons from 'react-native-vector-icons/Ionicons';import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';import Fontisto from 'react-native-vector-icons/Fontisto';import StarRating from '../components/StarRating';/**|--------------------------------------------------| Variables|--------------------------------------------------*/const { width, height } = Dimensions.get('window');const SCREEN_HEIGHT = height;const SCREEN_WIDTH = width;const ASPECT_RATIO = width / height;const LATITUDE_DELTA = 0.0422;const LONGITUDE_DELTA = LATITUDE_DELTA * ASPECT_RATIO;const darkTheme = false;/**|--------------------------------------------------| Component|--------------------------------------------------*/class ExploreScreen extends React.Component { /****** Props & States ******/ state = { region: { latitude: 0, longitude: 0, latitudeDelta: LATITUDE_DELTA, longitudeDelta: LONGITUDE_DELTA, }, items: [], markers: [], categories: [], currentMapRegion: null, }; /****** Functions ******/ ///when carousel item selected onCarouselItemSelected() { alert('carousel item selected'); } //when the carousel in scrolled onCarouselIndexChange(itemID) { const item = this.state.items.find(item => item.id == itemID); const marker = this.state.markers.find(marker => marker.id == itemID); const coordinates = { lat: item.lat, lon: item.lon }; this.goToLocation(coordinates); } //get current position getLocation(that) { Geolocation.getCurrentPosition( //get the current location position => { const region = { latitude: parseFloat(position.coords.latitude), longitude: parseFloat(position.coords.longitude), latitudeDelta: LATITUDE_DELTA, longitudeDelta: LONGITUDE_DELTA, }; that.setState({ region }); }, error => alert(error.message), { enableHighAccuracy: true, timeout: 20000 }, ); //get location on location change that.watchID = Geolocation.watchPosition(position => { const currentRegion = { latitude: position.coords.latitude, longitude: position.coords.longitude, latitudeDelta: LATITUDE_DELTA, longitudeDelta: LONGITUDE_DELTA, }; this.setState({ region: currentRegion }); }); } //move map to a lat/lon goToLocation(coordinates) { if (this.map) { this.map.animateToRegion({ latitude: coordinates.lat, longitude: coordinates.lon, latitudeDelta: LATITUDE_DELTA, longitudeDelta: LONGITUDE_DELTA, }); } } //when the region changes for the map onRegionChangeComplete(region) { //I dont know what to do here } //move map to center of current location gotToCenter() { if (this.map) { this.map.animateToRegion({ latitude: this.state.region.latitude, longitude: this.state.region.longitude, latitudeDelta: LATITUDE_DELTA, longitudeDelta: LONGITUDE_DELTA, }); } } //map the categories store in the state mapCategories = () => { const uniqueCategories = []; this.state.items.map(item => { if (uniqueCategories.indexOf(item.category) === -1) { uniqueCategories.push(item.category); } }); this.setState({ categories: uniqueCategories }); }; //map the items to markers and store in the state mapMarkers = () => { const markers = this.state.items.map(item => (<Marker key={item.id} coordinate={{ latitude: item.lat, longitude: item.lon }} image={require('../assets/map_marker.png')} tracksViewChanges={false} title={item.title} description={item.description} /> )); this.setState({ markers: markers }); }; /****** Lifecycle Functions ******/ async componentDidMount() { var that = this; //Checking for the permission just after component loaded if (Platform.OS === 'ios') { //for ios this.getLocation(that); } else { //for android async function requestLocationPermission() { try { const granted = await PermissionsAndroid.request( PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION, { title: 'Location Access Required', message: 'This App needs to Access your location', }, ); if (granted === PermissionsAndroid.RESULTS.GRANTED) { //To Check, If Permission is granted that.getLocation(that); } else { alert('Permission Denied'); } } catch (err) { alert('err', err); console.warn(err); } } requestLocationPermission(); } //get location on location change that.watchID = Geolocation.watchPosition(position => { const currentRegion = { latitude: parseFloat(position.coords.latitude), longitude: parseFloat(position.coords.longitude), latitudeDelta: LATITUDE_DELTA, longitudeDelta: LONGITUDE_DELTA, }; this.setState({ region: currentRegion }); }); console.log(this.state.region); const items = await ajax.fetchInitialItems(); this.setState({ items }); this.mapMarkers(); this.mapCategories(); } componentWillUnmount = () => { Geolocation.clearWatch(this.watchID); }; render() { const lat = this.state.region.latitude; const lon = this.state.region.longitude; if (this.state.items && lat && lon) { return (<View><MapView ref={map => { this.map = map; }} onRegionChangeComplete={this.onRegionChangeComplete.bind(this)} toolbarEnabled={false} showsMyLocationButton={false} provider={PROVIDER_GOOGLE} style={styles.map} customMapStyle={darkTheme ? mapDarkStyle : mapStandardStyle} showsUserLocation={true} followsUserLocation={true} region={this.state.region}> {this.state.markers}</MapView> {/* center button */}<TouchableOpacity onPress={this.gotToCenter.bind(this)} style={styles.centerButtonContainer}><Ionicons name="md-locate" size={20} /></TouchableOpacity><View style={styles.carousel}><MapCarousel data={this.state.items} onPressItem={() => { this.onCarouselItemSelected.bind(this); }} onUpdateLocation={this.onCarouselIndexChange.bind(this)} /></View></View> ); } else { return (<View style={styles.container}><Text style={styles.header}>Loading...</Text></View> ); } }}/**|--------------------------------------------------| Styles|--------------------------------------------------*/const styles = StyleSheet.create({ container: { ...StyleSheet.absoluteFillObject, height: '100%', width: '100%', justifyContent: 'center', alignItems: 'center', }, map: { height: '100%', }, carousel: { position: 'absolute', bottom: 25, }, header: { fontSize: 50, }, //character name name: { fontSize: 15, marginBottom: 5, }, //character image image: { width: 170, height: 80, }, //center button centerButtonContainer: { width: 40, height: 40, position: 'absolute', bottom: 260, right: 10, borderColor: '#191919', borderWidth: 0, borderRadius: 30, backgroundColor: '#d2d2d2', justifyContent: 'center', alignItems: 'center', shadowColor: '#000', shadowOffset: { width: 0, height: 9, }, shadowOpacity: 0.48, shadowRadius: 11.95, elevation: 18, opacity: 0.9, },});export default ExploreScreen;