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,
}}
/>
);
});
};
Related
What I'm trying to do is when the user clicks the position button in the map, to go to the user current location. For the location I'm using import * as Location from 'expo-location';
import React, { useState, useEffect } from 'react';
import MapView, { Marker, PROVIDER_GOOGLE } from 'react-native-maps';
import { StyleSheet, Image, Text, View } from 'react-native';
import * as Location from 'expo-location';
import { MaterialIcons } from '#expo/vector-icons';
import { LocationView, LocationBtn } from '../components/styles';
function Map(props) {
const [mapRegion, setMapRegion] = useState(null);
useEffect(() => {
(async () => {
const { status } = await Location.requestForegroundPermissionsAsync();
if (status !== 'granted') {
setErrorMsg('Permission to access location was denied');
return;
}
const location = await Location.getCurrentPositionAsync({});
setMapRegion({
longitude: location.coords.longitude,
latitude: location.coords.latitude,
longitudeDelta: 0.0922,
latitudeDelta: 0.0421,
});
console.log('location', location);
})();
}, []);
return (
<View style={{ flex: 1 }}>
<MapView
provider={PROVIDER_GOOGLE}
style={styles.map}
mapType={props.mapType}
// showsUserLocation={true}
loadingEnabled
initialRegion={mapRegion}
userInterfaceStyle="light"
showsTraffic
>
<Marker coordinate={mapRegion}>
<Image source={require('../assets/marker.png')} style={{ height: 90, width: 90 }} />
</Marker>
</MapView>
<LocationView>
<LocationBtn onPress={() => mapRegion}>
<MaterialIcons name="my-location" size={30} color="black" style={{ alignSelf: 'center', marginTop: 13 }} />
</LocationBtn>
</LocationView>
</View>
);
}
const styles = StyleSheet.create({
map: {
height: '100%',
},
});
export default Map;
By far I have put mapRegion state which it have the current location.
When I press the button, it doesn't work.
What do you mean by going to the user location? Looking at your code, you're saving your coordinates to the mapRegion state.
The onPress prop of LocationBtn has () => mapRegion which does nothing. It's just a function that has mapRegion return value.
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
}
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})
}
/>
I am trying to work on a simple proof of concept for geofencing around certain latitude and longitudes
Structure: - location service is turned on (no need background updates for now just initial location) - user location is determined - looks at data of locations in json file and finds the ones in close proximity #10km (refer to URL for API)
EXPO has recently updated the SDK to manage this, but with looking through multiple searches nothing really comes up with managing a simple task of placing static location to a database and then runs the proximity radius.
Using a basic setup for the concept but not sure next steps... Below is the setup that I have for now and would like some direction on where the GeoFence calls begins
import React, {Component} from 'react';
import { AppRegistry, Text, View, StyleSheet, FlatList, Image } from 'react-native';
import { Constants, MapView, Location, Permissions } from 'expo';
export default class App extends Component {
constructor() {
super ()
this.state = {
dataSource: []
}
}
state = {
mapRegion: null,
hasLocationPermissions: false,
locationResult: null
};
_handleMapRegionChange = mapRegion => {
console.log(mapRegion);
this.setState({ mapRegion });
};
_getLocationAsync = async () => {
let { status } = await Permissions.askAsync(Permissions.LOCATION);
if (status !== 'granted') {
this.setState({
locationResult: 'Permission to access location was denied',
});
} else {
this.setState({ hasLocationPermissions: true });
}
let location = await Location.getCurrentPositionAsync({});
this.setState({ locationResult: JSON.stringify(location) });
// Center the map on the location we just fetched.
this.setState({mapRegion: { latitude: location.coords.latitude, longitude: location.coords.longitude, latitudeDelta: 0.0922, longitudeDelta: 0.0421 }});
};
renderItem = ({item}) => {
return (
<View>
<View>
<Text>{item.code}
<Text>{item.name}
<Text>{item.lat}
<Text>{item.lon}
</Text>
</Text>
</Text>
</Text>
</View>
</View>
)}
componentDidMount() {
this._getLocationAsync();
//const url = 'https://www.json-generator.com/api/json/get/ccLAsEcOSq?indent=1'
const url = 'https://gist.githubusercontent.com/tdreyno/4278655/raw/7b0762c09b519f40397e4c3e100b097d861f5588/airports.json'
fetch(url)
.then((repsonse) => repsonse.json())
.then((responseJson) => {
this.setState({
dataSource: responseJson
})
})
.catch((error) => {
console.log(error)
})
}
render() {
return (
<View style={styles.container}>
<Text style={styles.paragraph}>
Pan, zoom, and tap on the map!
</Text>
{
this.state.locationResult === null ?
<Text>Finding your current location...</Text> :
this.state.hasLocationPermissions === false ?
<Text>Location permissions are not granted.</Text> :
this.state.mapRegion === null ?
<Text>Map region doesn't exist.</Text> :
<MapView
style={{ alignSelf: 'stretch', height: 200 }}
region={this.state.mapRegion}
onRegionChange={this._handleMapRegionChange}
/>
}
<Text>
Location: {this.state.locationResult}
</Text>
<View style={styles.container}>
<FlatList
//={[{ key: 'a'}, { key: 'b'}]}
//renderItem={({item }) => <Text>{item.key}</Text>}
data={this.state.dataSource}
renderItem={this.renderItem}
/>
</View>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
padding: 8,
},
paragraph: {
margin: 24,
fontSize: 18,
fontWeight: 'bold',
textAlign: 'center',
},
});
This is only proof of concept and just need some directions which will help me move on. Links and Tutorials anything will help
Thanks (still learning)
I am trying to send the latitude and longitude values(fetched using navigator.geolocation in home screen) to the detail screen, but instead of actual values, null value is being received by the details page.This is my code:
import React from 'react';
import { View, Text, Button } from 'react-native';
import { createStackNavigator } from 'react-navigation';
class HomeScreen extends React.Component {
constructor(props) {
super(props);
this.state = {
latitude: null,
longitude: null,
error: null,
};
}
render() {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Button title="Add New Outlet" onPress={() => {
navigator.geolocation.getCurrentPosition(
(position) => {
this.setState({
latitude: position.coords.latitude,
longitude: position.coords.longitude,
error: null,
});
},
(error) => this.setState({ error: error.message }),
{ enableHighAccuracy: true, timeout: 20000, maximumAge: 1000 },
);
this.props.navigation.navigate('Details', {latitude: this.state.latitude, longitude: this.state.longitude,});
}}/>
</View>
);
}
}
class DetailsScreen extends React.Component {
render() {
const { navigation } = this.props;
const latitude = navigation.getParam('latitude', 'NO-ID');
const longitude = navigation.getParam('longitude', 'some default value');
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Details Screen</Text>
<Text>latitude: {JSON.stringify(latitude)}</Text>
<Text>longitude: {JSON.stringify(longitude)}</Text>
</View>
);
}
}
const RootStack = createStackNavigator(
{
Home: HomeScreen,
Details: DetailsScreen,
},
{
initialRouteName: 'Home',
}
);
export default class App extends React.Component {
render() {
return <RootStack />;
}
}
The values are getting passed successfully between the components but current state values are not getting passed.Please help...
This can be an issue of the asynchronous nature of this.setState(). Try modifying the code a little bit. Instead of calling this.props.navigation.navigate() after the this.setState(), try calling it within the this.setState() in a callback as a second parameter. Here is how you can do it:
this.setState({
latitude: position.coords.latitude,
longitude: position.coords.longitude,
error: null,
}, () => {
this.props.navigation.navigate('Details', {latitude: this.state.latitude, longitude: this.state.longitude,})
});
I hope that helps. Let me know if it works.