React Native Maps animate to bearing after initial render - react-native

I am trying to call a method on the map right after the component first renders. In this case this.map is undefined, but shouldn't it be set by the ref? How do I get a reference to the MapView in the componentDidMount method?
import React from 'react';
import { MapView } from 'expo';
export default class Map extends React.Component {
componentDidMount() {
this.map.animateToBearing(25)
}
render() {
return (
<MapView
ref={ref => { this.map = ref }}
style={{ flex: 1 }}
mapType="satellite"
initialRegion={{
latitude: 39.2741004,
longitude: -76.6502307,
latitudeDelta: 0.002,
longitudeDelta: 0.001,
}}
/>
);
}
}

Looking at this Github issue, you probably have to use onLayout instead of componentDidMount.
For example:
<MapView
ref={ref => { this.map = ref }}
onLayout={() => this.map.animateToBearing(25)}
....
/>

const [heading, setHeading] = useState(0);
const cameraView = {
center: {
latitude: lat,
longitude: lng,
},
pitch: 10,
heading: heading,
altitude: 1,
zoom: 15
};
let getHeading = () => {
Location.watchHeadingAsync(value => {
setHeading(value.magHeading)
});
};
useEffect(()=>{
initialLocation();
getHeading();
}, [])
By using watchHeadingAsync you can update the heading constantly.

Related

React Native render error: null is not an object (evaluating origin.location)

I'm fairly new to react-native and decided to try cloning an app, following a tutorial. I'm trying to pass data stored in redux to a map rendered on my home screen but when I run, I keep getting the following error: null is not an object (evaluating origin.location). Any suggestions, solutions and quick fixes are welcome. Thanks
Home screen codes
import { View, TouchableOpacity, Text } from 'react-native'
import { React } from 'react'
import HeaderTabs from '../components/HeaderTabs'
import { GooglePlacesAutocomplete } from 'react-native-google-places-autocomplete'
import { Ionicons } from '#expo/vector-icons'
import { setDestination, setOrigin } from '../slices/navSlice'
import { useDispatch } from 'react-redux'
import { SafeAreaView } from 'react-native-safe-area-context'
import { GOOGLE_MAPS_APIKEY } from '#env'
import Map from './components/Map'
const Home = ({navigation}) =>{
const dispatch = useDispatch();
return (
<SafeAreaView style={{ backgroundColor:"white",
flex:1,
padding:5,
marginTop:-20
}}>
<View style={{alignItems:"flex-end"}} >
<TouchableOpacity onPress={() => {navigation.navigate("CustomerInfo")}}>
<Ionicons name="person-circle" size={30} color="black" />
</TouchableOpacity>
</View>
<HeaderTabs />
<View style={{marginTop:15, flexDirection:"row"}}>
<GooglePlacesAutocomplete
query={{key: GOOGLE_MAPS_APIKEY}}
enablePoweredByContainer={false}
onPress={(data, details = null) => {
dispatch(setOrigin({
location: details.geometry.location,
description: data.description,
})
);
dispatch(setDestination(null));
}}
fetchDetails={true}
returnKeyType={"search"}
placeholder='Enter package pickup location'
debounce={400}
nearbyPlacesAPI='GooglePlacesSearch'
styles={{
textInput:{
backgroundColor:"#eee",
borderRadius:15,
fontFamily:"Poppins_500Medium",
marginTop:10,
fontSize:18,
},
textInputContainer:{
backgroundColor:"#eee",
borderRadius:15,
flexDirection:"row",
alignItems:"center",
width:380
},
}}
renderLeftButton={() => (
<View style={{
marginLeft:10
}}>
<Ionicons name="location" size={20} color="green" />
</View>
)}
renderRightButton={() => (
<View style={{
flexDirection:"row",
marginRight:5,
backgroundColor:"white",
padding:9,
borderRadius:30,
alignItems:"center"
}}>
<Ionicons name="search" size={15} color="black" />
</View>
)}
/>
</View>
<Map />
</SafeAreaView>
)};
export default Home;
Map component codes
import { View, Text } from 'react-native'
import React from 'react'
import MapView, { Marker } from 'react-native-maps'
import { useSelector } from 'react-redux'
import { selectOrigin } from '../slices/navSlice'
const Map = () => {
const origin = useSelector(selectOrigin);
return (
<MapView
style={{flex:1, marginTop:10}}
initialRegion={{
latitude: origin.location.lat,
longitude: origin.location.lng,
latitudeDelta: 0.0922,
longitudeDelta: 0.0421,
}}
/>
);
};
export default Map;
Where origin.location is coming from
const initialState = {
origin: null,
destination: null,
travelTimeInformation: null
}
export const navSlice = createSlice({
name: "nav",
initialState,
reducer: {
setOrigin: (state, action) => {
state.origin = action.payload;
},
setDestination: (state, action) => {
state.destination = action.payload;
},
setTravelTimeInformation: (state, action) => {
state.travelTimeInformation = action.payload;
},
},
});
export const { setOrigin, setDestination, setTravelTimeInformation } = navSlice.actions;
export const selectOrigin = (state) => state.nav.origin;
export const selectDestination = (state) => state.nav.destination;
export const selectTravelTimeInformation = (state) => state.nav.travelTimeInformation;
export default navSlice.reducer;
Issue
The origin state value is initially null.
const initialState = {
origin: null, // <-- initially null here
destination: null,
travelTimeInformation: null
}
And the code is attempting to access into this null value.
const Map = () => {
const origin = useSelector(selectOrigin);
return (
<MapView
style={{flex:1, marginTop:10}}
initialRegion={{
latitude: origin.location.lat,
longitude: origin.location.lng,
latitudeDelta: 0.0922,
longitudeDelta: 0.0421,
}}
/>
);
};
Solutions
You've a few options. Here are 4, there may be others.
Conditionally render the MapView component on the origin state being populated.
const Map = () => {
const origin = useSelector(selectOrigin);
return origin?.location
? (
<MapView
style={{flex:1, marginTop:10}}
initialRegion={{
latitude: origin.location.lat,
longitude: origin.location.lng,
latitudeDelta: 0.0922,
longitudeDelta: 0.0421,
}}
/>
) : null;
};
Use Optional Chaining operator on the origin access and provide a fallback value.
const Map = () => {
const origin = useSelector(selectOrigin);
return (
<MapView
style={{flex:1, marginTop:10}}
initialRegion={{
latitude: origin?.location?.lat ?? 0, // use fallback value that makes sense
longitude: origin?.location?.lng ?? 0, // use fallback value that makes sense
latitudeDelta: 0.0922,
longitudeDelta: 0.0421,
}}
/>
);
};
Provide a valid, defined origin value from the selector.
const defaultOrigin = { location: { lat: 0, lng: 0 } }; // use origin value that makes sense
export const selectOrigin = (state) => state.nav.origin ?? defaultOrigin;
Or just provide a more useful initial state value.
const defaultOrigin = { location: { lat: 0, lng: 0 } }; // use origin value that makes sense
const initialState = {
origin: defaultOrigin,
destination: null,
travelTimeInformation: null
}

Undefined is not an object in React native when rendering

I'm new to React and React native and am trying to retrieve data from an API and then render it but I seem to be running into problems.
I'm getting errors like:
undefined is not an object (evaluating 'attractions.map') in RenderPhotos_render
I may have jumped into React Native too early...So excuse my lack of knowledge!
import React, {Component} from 'react';
import {StyleSheet, View, ActivityIndicator} from 'react-native';
import MapView, {Marker} from 'react-native-maps';
import {connect} from 'react-redux';
const mapStateToProps = state => {
return {
attractions: state.attractions.attractions,
};
};
const mapDispatchToProps = dispatch => {
return {
GET_Attractions(callback) {
dispatch({type: 'attractions/GET_Attractions', callback});
},
};
};
export default connect(
mapStateToProps,
mapDispatchToProps,
)(
class Home extends Component {
state = {
loading: true,
};
componentDidMount = () => {
const callback = () => {
this.setState({
loading: false,
});
};
this.props.GET_Attractions(callback);
};
renderMapMarker = () => {
const {attractions} = this.props;
return attractions.map(marker => {
return (
<Marker
key={marker.caseId}
title={marker.caseName}
description="點擊查看詳細資料"
coordinate={{
latitude: marker.latitude,
longitude: marker.longitude,
}}
/>
);
});
};
render() {
if (this.state.loading) {
return (
<View style={styles.container}>
<ActivityIndicator />
</View>
);
} else {
return (
<View style={styles.container}>
<MapView
style={styles.mapView}
initialRegion={{
latitude: 24.149706,
longitude: 120.683813,
latitudeDelta: 8,
longitudeDelta: 8,
}}>
{this.renderMapMarker()}
</MapView>
</View>
);
}
}
},
);
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
mapView: {
width: '100%',
height: '100%',
},
});
By default attractions might be undefined so you can check to validate first then render if it has data to render like
render() {
if (this.state.loading) {
return (
...
);
} else {
return (
<View style={styles.container}>
<MapView
...
>
{this.props.attractions && this.renderMapMarker()} // and this
</MapView>
</View>
);
}
}
You are trying to use a prop "attractions" that is being populated from the reducer, so initially when your screen is being rendered the prop "attractions" will be undefined so you need to achieve this with the condition to make the error go away.
{attractions && this.renderMapMarker()}
Just add ? after attractions and it should work.
renderMapMarker = () => {
const {attractions} = this.props;
return attractions?.map(marker => {
return (
<Marker
key={marker.caseId}
title={marker.caseName}
description="點擊查看詳細資料"
coordinate={{
latitude: marker.latitude,
longitude: marker.longitude,
}}
/>
);
});
};

Having problems rendering contents from ComponentDidMount to <View>

How do i go about this? I am trying to render the contents of Initial region, so that once the Application Loads it automatically gets the Longitude and Latitude of the Location and put on the screen.
I am just a bit confused as to what and how to go about it, My source code is given thus
import React, { Component } from 'react';
import { View, Text , StyleSheet, Dimensions } from 'react-native';
import MapView from 'react-native-maps';
export default class HomePage extends Component {
constructor(props) {
super(props);
}
componentDidMount(){
navigator.geolocation.getCurrentPosition((position)=>{
var lat = parseFloat(position.coords.latitude)
var long = parseFloat(position.coords.longitude)
var initialRegion ={
latitude: lat,
longitude: long,
latitudeDelta: 0.0922,
longitudeDelta: 0.0421
}
this.setState({ initialRegion: initialRegion })
},
(error) => aalert(JSON.stringify(error)),
{
enableHighAccuracy: true, timeout: 20000, maximumAge: 1000
});
}
render() {
return (
<View style={styles.container}>
<MapView style={styles.map}
initialRegion={this.state.initialRegion}
showsUserLocation={true}>
</MapView>
</View>
);
}
}
const styles=StyleSheet.create({
container:{
flex: 1,
},mapContainer: {
flex: 1,
},
map: {
flex: 1,
width: Dimensions.get("window").width,
height: Dimensions.get("window").height,
}
});
Any candid advice as to how I can do something like that? Kindly assist.
I would guess the your map loads with no initialRegion as you are doing a request after the first render (componentDidMount) you may try to prevent the map from loading before you have the necessary information, I've also started your state on constructor. The code would look like this:
import React, { Component } from 'react';
import { View, Text , StyleSheet, Dimensions } from 'react-native';
import MapView from 'react-native-maps';
export default class HomePage extends Component {
constructor(props) {
super(props);
this.state = {
initialRegion: null
}
}
componentDidMount(){
navigator.geolocation.getCurrentPosition((position)=>{
var lat = parseFloat(position.coords.latitude)
var long = parseFloat(position.coords.longitude)
var initialRegion = {
latitude: lat,
longitude: long,
latitudeDelta: 0.0922,
longitudeDelta: 0.0421
}
this.setState({ initialRegion: initialRegion })
},
(error) => aalert(JSON.stringify(error)),
{
enableHighAccuracy: true, timeout: 20000, maximumAge: 1000
});
}
render() {
return (
<View style={styles.container}>
{this.state.initialRegion !== null && (
<MapView style={styles.map}
initialRegion={this.state.initialRegion}
showsUserLocation={true} />
)}
</View>
);
}
}
Wish success on your project.

"NSCFBoolean objectForKeyedSubscript:]: unrecognized selector sent to instance" in React Native

I am using React Native with Expo and this was originally a class based component but I converted it to a functional component because I want to use hooks. Now it is throwing an error shown in the screenshot. I am not sure what to do about the error?I have seen one other posting about this, but was a little lost (I am practicing with react). The component is basically a GPS with a marker! Thank you
import { View, Text, Animated, StyleSheet } from "react-native";
import MapView, { Marker, PROVIDER_GOOGLE } from "react-native-maps";
import React, { Component, useState } from "react";
import { MaterialCommunityIcons } from "react-native-vector-icons";
const LATITUDE = 18.7934829;
const LONGITUDE = 98.9867401;
const LATITUDE_DELTA = 0.009;
const LONGITUDE_DELTA = 0.009;
export default function MapLocation() {
const [location, setLocation] = useState({
isLoading: true,
latitude: LATITUDE,
longitude: LONGITUDE,
error: null,
});
var getMapRegion = () => ({
latitude: location.latitude,
longitude: location.longitude,
latitudeDelta: LATITUDE_DELTA,
longitudeDelta: LONGITUDE_DELTA,
});
navigator.geolocation.getCurrentPosition(
(position) => {
console.log(position);
setLocation({
latitude: position.coords.latitude,
longitude: position.coords.longitude,
error: null,
});
},
(error) => setLocation({ error: error.message }),
{ enableHighAccuracy: false, timeout: 200000, maximumAge: 1000 }
);
navigator.geolocation.watchPosition((position) => {
const { latitude, longitude } = position.coords;
setLocation({ latitude, longitude });
});
const { isLoading } = location;
return (
<View style={{ flex: 1 }}>
<MapView
style={{ flex: 1 }}
provider={PROVIDER_GOOGLE}
region={getMapRegion}
showsUserLocation={true}
showsMyLocationButton={true}
>
<Marker coordinate={getMapRegion}>
<MaterialCommunityIcons name="egg" color={"white"} size={35} style={styles.shadow} />
</Marker>
<Marker
coordinate={{ latitude: 34.0198536, longitude: -80.923467 }}
pinColor="maroon"
title={"title"}
description={"description"}
>
<MaterialCommunityIcons name="school" color={"maroon"} size={40} style={styles.shadow} />
</Marker>
</MapView>
</View>
);
}
const styles = StyleSheet.create({
shadow: {
// transform: [{ rotateZ: "10deg" }],
shadowColor: "black",
shadowOffset: {
width: 0,
height: 1,
},
shadowOpacity: 0.5,
shadowRadius: 2,
elevation: 3,
},
});
There were several problems with my conversion to hooks using useState and useEffect. Both of which I solved with this guide on converting classes to functions. I was not using the "useEffect hook" in place of component did mount, I also did not have an empty array at the end of the useEffect hook which made it re-render constantly from what I understand.
Link to functional component conversion steps

React Native Maps. Google maps returns back to initial location unable to drag the map around

Unable to drag the map around, after using google auto complete to search for the location. The map is able to change to the location. However I am unable to drag the map around, its stuck to the center of the location that I searched.
MapContainer.js is used to render the map and componentdidmount is used to retrieve the coordinates of busstops which will be displayed as markers under the render below
import { View } from "react-native";
import MapInput from "./MapInput";
import { getLocation, geocodeLocationByName } from "./location-service";
import React, { Component } from "react";
import MapView, { Marker, StyleSheet } from "react-native-maps";
const MyMapView = (props) => {
return (
<MapView
style={{ flex: 1 }}
region={props.region}
showsUserLocation={true}
onRegionChange={(reg) => props.onRegionChange(reg)}
>
{props.busstopArray.map((item) => {
return (
<Marker
key={item.bus_stop_id}
coordinate={{
latitude: Number(item.latitude),
longitude: Number(item.longitude),
}}
title={item.name}
></Marker>
);
})}
</MapView>
);
};
class MapContainer extends React.Component {
state = {
region: {},
busstop: [], //busstop is an empty array, in which the JSON values will be added, to be used later in the code.
};
componentDidMount() {
this.getInitialState();
const requestOptions = {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ route_id: "1" }, { route_id: "2" }),
};
fetch(
"https://laravelsyd-fypfinalver.herokuapp.com/getBusStop",
requestOptions
)
.then((response) => response.json())
.then((data) => {
this.setState({ busstop: data });
});
}
getInitialState() {
getLocation().then((data) => {
//import locationservice
console.log(data); //prints out intial region coords
this.setState({
region: {
latitude: data.latitude,
longitude: data.longitude,
latitudeDelta: 0.003,
longitudeDelta: 0.003,
},
});
});
}
getCoordsFromName(loc) {
this.setState({
region: {
latitude: loc.lat,
longitude: loc.lng,
latitudeDelta: 0.003,
longitudeDelta: 0.003,
},
});
}
onMapRegionChange(region) {
this.setState({ region });
}
render() {
const bus_stop = [...this.state.busstop];
// console.log(bus_stop); //testing to see if can display the json objects and it can
return (
<View style={{ flex: 1 }}>
<View style={{ flex: 1 }}>
<MapInput notifyChange={(loc) => this.getCoordsFromName(loc)} />
</View>
{this.state.region["latitude"] ? (
<View style={{ flex: 1 }}>
<MyMapView
region={this.state.region}
onRegionChange={(reg) => this.onMapRegionChange(reg)}
busstopArray={bus_stop}
/>
</View>
) : null}
</View>
);
}
}
export default MapContainer;
you have to add draggable in Marker:
<Marker
draggable
coordinate={{
latitude: Number(this.state.location.latitude),
longitude: Number(this.state.location.longitude),
}}
onDragEnd={e =>
this.setState({location: e.nativeEvent.coordinate})
}
/>