React Native Opening Native Maps - google-maps

I am trying to get my button onPress function to call a function that will open apple maps or google maps depending on the device. For some reason nothing is happening when I press the button.
Here is my function:
openMap= () => {
console.log('open directions')
Platform.select({
ios: () => {
Linking.openURL('http://maps.apple.com/maps?daddr=');
},
android: () => {
Linking.openURL('http://maps.google.com/maps?daddr=');
}
});
}
Here is the button:
<TouchableOpacity
onPress={this.openMap}
style={styles.navigateBtn}>
<Icon name="md-navigate" style={{ fontSize: 24 }} />
<Text
style={{ fontSize: 13, fontWeight: "700", lineHeight: 14 }}
>
NAVIGATE
</Text>
</TouchableOpacity>
Eventually I want to pass longitude and latitude into the openMap function to get directions but first I need to get the map to open.
Here is my import
import { View, TouchableOpacity, Modal, Platform, Alert, StyleSheet, Linking } from "react-native";
import {Header, Content, Text, Button, Icon, Card,CardItem, Title, Left, Right, Body, Container
} from "native-base";
import { Constants } from 'expo

Your button seem to call this.Map in the onPress of your TouchableOpacity. It should not be this.openMap ?
Hope it's help you!
EDIT:
Try to declare your function like this inside of your component:
openMap() {
console.log('open directions')
Platform.select({
ios: () => {
Linking.openURL('http://maps.apple.com/maps?daddr=');
},
android: () => {
Linking.openURL('http://maps.google.com/maps?daddr=');
}
});
}
And for your TouchableOpacity try this
<TouchableOpacity
onPress={() => this.openMap() }
style={styles.navigateBtn}>
<Icon name="md-navigate" style={{ fontSize: 24 }} />
<Text
style={{ fontSize: 13, fontWeight: "700", lineHeight: 14 }}
>

This will oepn maps in the web, then redirect to the app (if it is installed):
const openMaps = (latitude, longitude) => {
const daddr = `${latitude},${longitude}`;
const company = Platform.OS === "ios" ? "apple" : "google";
Linking.openURL(`http://maps.${company}.com/maps?daddr=${daddr}`);
}
Although i would just use this library, which uses deep linking instead:

openMap= () => {
console.log('open directions')
let f = Platform.select({
ios: () => {
Linking.openURL('http://maps.apple.com/maps?daddr=38.7875851,-9.3906089');
},
android: () => {
console.log('ANDROID')
Linking.openURL('http://maps.google.com/maps?daddr=38.7875851,-9.3906089').catch(err => console.error('An error occurred', err));;
}
});
f();
}

Related

How to make MapView marker stay at given coordinate when zooming or pinching?

I'm currently working with the MapView.Marker on React Native, which now I can set the marker location through the code and it all seems to work fine until I zoom in/out or pinch the screen like this
as the images above is shown that the marker has been slightly move out of its location
the code I use to pin the marker is
onRegionChange = region => {
this.setState({
region
})
}
setMarkerRegion = () => {
const { region } = this.state;
this.setState({
markerRegion: region,
locationMarked: true,
});
}
<MapView
ref={m => { this.map = m }}
style={{ flex: 10 }}
initialRegion={region}
onRegionChangeComplete={this.onRegionChange}
provider={PROVIDER_GOOGLE}
customMapStyle={customStyles}>
<Marker
coordinate={{
latitude: markerRegion.latitude,
longitude: markerRegion.longitude
}}
style={{ alignSelf: 'center' }}
title={"This is a title <3"}
description={searchLocation}
/>
</MapView>
<TouchableOpacity style={[styles.markerFixed]} onPress={this.setMarkerRegion}>
<Icon name={'map-marker'} color={'white'} type='font-awesome' containerStyle={styles.marker}></Icon>
</TouchableOpacity>
I'm try searching on SO but there are only an example on native android which is not helpful in this case, any idea would be appreciated.

How to get the address from google maps autocomplete in React Native

I am using react-native-google-places-autocomplete to select a location. I want to extract the location selected and use it in other component.
How can I save the address
This is my code
import React, {Component} from 'react';
import { View, Image, Text, StyleSheet } from 'react-native';
import { GooglePlacesAutocomplete } from 'react-native-google-places-autocomplete';
const homePlace = { description: 'Home', geometry: { location: { lat: 48.8152937, lng: 2.4597668 } }};
const workPlace = { description: 'Work', geometry: { location: { lat: 48.8496818, lng: 2.2940881 } }};
export default class google extends Component {
render(){
return (
<View style={styles.container}>
<View style={styles.top}>
<GooglePlacesAutocomplete
placeholder='Search'
minLength={2} // minimum length of text to search
autoFocus={false}
returnKeyType={'search'} // Can be left out for default return key https://facebook.github.io/react-native/docs/textinput.html#returnkeytype
listViewDisplayed='auto' // true/false/undefined
fetchDetails={true}
renderDescription={row => row.description} // custom description render
onPress={(data, details = null) => { // 'details' is provided when fetchDetails = true
console.log(data, details);
this.setState(
{
address: data.description, // selected address
coordinates: `${details.geometry.location.lat},${details.geometry.location.lng}` // selected coordinates
}
);
}}
getDefaultValue={() => ''}
query={{
// available options: https://developers.google.com/places/web-service/autocomplete
key: 'AIzaS#################',
language: 'es', // language of the results
}}
styles={{
textInputContainer: {
width: '100%'
},
description: {
fontWeight: 'bold'
},
predefinedPlacesDescription: {
color: '#1faadb'
}
}}
currentLocation={true} // Will add a 'Current location' button at the top of the predefined places list
currentLocationLabel="Current location"
nearbyPlacesAPI='GooglePlacesSearch' // Which API to use: GoogleReverseGeocoding or GooglePlacesSearch
GoogleReverseGeocodingQuery={{
// available options for GoogleReverseGeocoding API : https://developers.google.com/maps/documentation/geocoding/intro
}}
GooglePlacesSearchQuery={{
// available options for GooglePlacesSearch API : https://developers.google.com/places/web-service/search
rankby: 'distance',
types: 'food'
}}
filterReverseGeocodingByTypes={['locality', 'administrative_area_level_3']} // filter the reverse geocoding results by types - ['locality', 'administrative_area_level_3'] if you want to display only cities
predefinedPlaces={[homePlace, workPlace]}
debounce={200} // debounce the requests in ms. Set to 0 to remove debounce. By default 0ms.
renderRightButton={() => <Text>Custom text after the input</Text>}
/>
</View>
<View style={styles.container2}>
<Text>
hola {this.setState.address}
</Text>
</View>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
width: '100%',
height: '100%',
},
welcome: {
fontSize: 40,
textAlign: 'center',
margin: 10,
color:'black',
},
instructions: {
textAlign: 'center',
color: 'black',
marginBottom: 5,
},
top: {
height: '50%',
justifyContent: 'center',
alignItems: 'center',
},
container2: {
height:'75%',
justifyContent: 'center',
},
buttonContainer: {
alignItems: 'center',
backgroundColor: 'rgba(255, 255,255, 0.5)',
padding: 1,
margin: 50,
borderRadius: 25,
borderWidth: 2,
borderColor: '#0B0B3B'
},
textoboton: {
textAlign: 'center',
color: 'black',
marginBottom: 5,
fontSize: 12
},
})
I've been also using another library with some differences
app.js
import React,{Component} from 'react';
import { Constants } from 'expo';
import Icon from 'react-native-vector-icons/FontAwesome';
import { View, Image, Text, StyleSheet, AsyncStorage, Button,ScrollView, TextInput, ActivityIndicator } from 'react-native';
import {
NavigationActions
} from 'react-navigation';
import { GoogleAutoComplete } from 'react-native-google-autocomplete';
import {Card, Input} from "react-native-elements";
import LocationItem from './locationItem';
export default class App extends React.Component {
state={
datos:[],
}
componentDidMount(){
this._loadedinitialstate().done();
}
_loadedinitialstate =async() => {
AsyncStorage.getItem('datos');
}
render() {
return (
<View style={styles.container}>
<GoogleAutoComplete apiKey={'AIzaSyB2HyNTBm1sQJYJkwOOUA1LXRHAKh4gmjU'} debounce={20} minLength={2} getDefaultValue={() => ''} onPress={(data, details = null) => { // 'details' is provided when fetchDetails = true
console.log(data, details);}} returnKeyType={'default'} fetchDetails={true}
>
{({
locationResults,
isSearching,
clearSearchs,
datos,
handleTextChange
}) => (
<React.Fragment>
<View style={styles.inputWrapper}>
<Input
style={styles.textInput}
placeholder="Ingresa tu direccion"
onChangeText={(datos) => this.setState({datos})}
value={datos}
/>
</View>
{isSearching && <ActivityIndicator size="large" color="red" />}
<ScrollView>
{locationResults.map(el => (
<LocationItem
{...el}
key={el.id}
/>
))}
</ScrollView>
</React.Fragment>
)}
</GoogleAutoComplete>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
textInput: {
height: 40,
width: 300,
borderWidth: 1,
paddingHorizontal: 16,
},
inputWrapper: {
marginTop: 80,
flexDirection: 'row'
},
});
locationitem.js
import React, { PureComponent } from 'react';
import { View, Alert, Text, StyleSheet, TouchableOpacity, AsyncStorage} from 'react-native';
class LocationItem extends PureComponent {
constructor(props) {
super(props);
this.state = {datos:''};
}
_handlePress = () => {
AsyncStorage.setItem('datos',datos)
}
render() {
return (
<TouchableOpacity style={styles.root} onPress={this._handlePress} >
<Text value={this.state.datos}> {this.props.description} </Text>
</TouchableOpacity>
);
}
}
const styles = StyleSheet.create({
root: {
height: 40,
borderBottomWidth: StyleSheet.hairlineWidth,
justifyContent: 'center'
}
})
export default LocationItem;
The source of both codes is here react-native-google-places-autocomplete enter link description here
Which code will be easy to use, and How can I solve my Issue (get the address) ??
Any Answer will be Helpful
first install
npm i react-native-google-places-autocomplete
then
import React from 'react';
import { View, Image } from 'react-native';
import { GooglePlacesAutocomplete } from 'react-native-google-places-autocomplete';
const homePlace = { description: 'Home', geometry: { location: { lat: 48.8152937, lng: 2.4597668 } }};
const workPlace = { description: 'Work', geometry: { location: { lat: 48.8496818, lng: 2.2940881 } }};
const GooglePlacesInput = () => {
return (
<GooglePlacesAutocomplete
placeholder='Search'
minLength={2} // minimum length of text to search
autoFocus={false}
returnKeyType={'search'} // Can be left out for default return key https://facebook.github.io/react-native/docs/textinput.html#returnkeytype
listViewDisplayed='auto' // true/false/undefined
fetchDetails={true}
renderDescription={row => row.description} // custom description render
onPress={(data, details = null) => { // 'details' is provided when fetchDetails = true
console.log(data, details);
}}
getDefaultValue={() => ''}
query={{
// available options: https://developers.google.com/places/web-service/autocomplete
key: 'YOUR API KEY',
language: 'en', // language of the results
types: '(cities)' // default: 'geocode'
}}
styles={{
textInputContainer: {
width: '100%'
},
description: {
fontWeight: 'bold'
},
predefinedPlacesDescription: {
color: '#1faadb'
}
}}
currentLocation={true} // Will add a 'Current location' button at the top of the predefined places list
currentLocationLabel="Current location"
nearbyPlacesAPI='GooglePlacesSearch' // Which API to use: GoogleReverseGeocoding or GooglePlacesSearch
GoogleReverseGeocodingQuery={{
// available options for GoogleReverseGeocoding API : https://developers.google.com/maps/documentation/geocoding/intro
}}
GooglePlacesSearchQuery={{
// available options for GooglePlacesSearch API : https://developers.google.com/places/web-service/search
rankby: 'distance',
types: 'food'
}}
filterReverseGeocodingByTypes={['locality', 'administrative_area_level_3']} // filter the reverse geocoding results by types - ['locality', 'administrative_area_level_3'] if you want to display only cities
predefinedPlaces={[homePlace, workPlace]}
debounce={200} // debounce the requests in ms. Set to 0 to remove debounce. By default 0ms.
renderLeftButton={() => <Image source={require('path/custom/left-icon')} />}
renderRightButton={() => <Text>Custom text after the input</Text>}
/>
);
}
After a long journey, these steps helped me solve the problem.
1) Install npm install react-native-google-places-autocomplete --save.
2) Then use this code below, as an element in your component.
<GooglePlacesAutocomplete
query={{
key: "GOOGLE_PLACES_API_KEY",
language: "en", // language of the results
}}
onPress={(data, details = null) => {
console.log(details);
console.log(data);
console.log("data.description",data.description.split(','));
}}
onFail={(error) => console.error(error)}
listViewDisplayed="false"
requestUrl={{
url:
"https://cors-
anywhere.herokuapp.com/https://maps.googleapis.com/maps/api",
useOnPlatform: "web",
}} // this in only required for use on the web. See https://git.io/JflFv
more for details.
styles={{
textInputContainer: {
backgroundColor: "rgba(0,0,0,0)",
borderTopWidth: 0,
borderBottomWidth: 0,
},
textInput: {
marginLeft: 0,
marginRight: 0,
height: 38,
color: "#5d5d5d",
fontSize: 16,
},
predefinedPlacesDescription: {
color: "#1faadb",
},
}}
/>
3) You may have the same problem that i had, which the list disappears when i try to select result. However, this is the action that solved this problem for me.
Commenting out onBlur on node_modules. path: "..\node_modules\react-native-google-places-autocomplete\GooglePlacesAutocomplete.js".
...
onFocus={onFocus ? () => {this._onFocus(); onFocus()} : this._onFocus}
// onBlur={this._onBlur}
underlineColorAndroid={this.props.underlineColorAndroid}
...
4) For saving the address you can check the console.log in the code, and then use setState or something like this.
5) For more information and options of these element check out this repository:
https://github.com/FaridSafi/react-native-google-places-autocomplete.
hope this will help for you :)
First of all, I used listViewDisplayed={false} because otherwise the list view get stuck with the results, and even on location press the list doesn't closes.
Second, to answer your question: The results are in the onPress function of GooglePlacesAutocomplete, you can update the state with the chosen location and then use it where ever you want in your component:
onPress={(data, details = null) => {
this.setState({
latitude: details.geometry.location.lat,
longitude: details.geometry.location.lng,
moveToUserLocation: true
});
this._gotoLocation();
}
}
As i wrote it, onPress also trigger the function that moves the map to display the new location.
import React, {Component} from 'react';
import { View, Image, Text, StyleSheet } from 'react-native';
import { GooglePlacesAutocomplete } from 'react-native-google-places-autocomplete';
export default class google extends Component {
constructor(props) {
super(props);
this.state = {
address:null,
lat:null,
lng:null,
}
}
getAdd(data){
console.log("add",data);
this.setState(
{
address: data.formatted_address, // selected address
lat: data.geometry.location.lat,// selected coordinates latitude
lng:data.geometry.location.lng, // selected coordinates longitute
}
);
console.log("this.state.address",this.state.address); ///to console address
console.log("this.state.coordinates",this.state.lat,this.state.lng); /// to console coordinates
}
render(){
return (
<View style={styles.container}>
<View style={styles.top}>
<GooglePlacesAutocomplete
placeholder='Search'
minLength={2} // minimum length of text to search
autoFocus={false}
fetchDetails={true}
returnKeyType={'default'}
onPress={(data, details = null) => { // 'details' is provided when fetchDetails = true
var data = details;
this.getAdd(data);
}}
query={{
// available options: https://developers.google.com/places/web-service/autocomplete
key: 'AIzaS#################',
language: 'en',
types: 'geocode', // default: 'geocode'
}}
listViewDisplayed={this.state.showPlacesList}
textInputProps={{
onFocus: () => this.setState({ showPlacesList: true }),
onBlur: () => this.setState({ showPlacesList: false }),
}}
styles={{
textInputContainer: {
width: '100%'
},
description: {
fontWeight: 'bold'
},
predefinedPlacesDescription: {
color: '#1faadb'
}
}}
currentLocation={true} // Will add a 'Current location' button at the top of the predefined places list
currentLocationLabel="Current location"
nearbyPlacesAPI='GooglePlacesSearch' // Which API to use: GoogleReverseGeocoding or GooglePlacesSearch
filterReverseGeocodingByTypes={['locality', 'administrative_area_level_3']} // filter the reverse geocoding results by types - ['locality', 'administrative_area_level_3'] if you want to display only cities
// predefinedPlaces={[]}
predefinedPlacesAlwaysVisible={true}
/>
</View>
{ this.state.address !=null ?(
<View style={styles.container2}>
<Text>
Address: {this.state.address}
</Text>
</View>
):null }
</View>
);
}
}
const styles = StyleSheet.create({
container: {
width: '100%',
height: '100%',
},
welcome: {
fontSize: 40,
textAlign: 'center',
margin: 10,
color:'black',
},
instructions: {
textAlign: 'center',
color: 'black',
marginBottom: 5,
},
top: {
height: '50%',
justifyContent: 'center',
alignItems: 'center',
},
container2: {
height:'75%',
justifyContent: 'center',
},
buttonContainer: {
alignItems: 'center',
backgroundColor: 'rgba(255, 255,255, 0.5)',
padding: 1,
margin: 50,
borderRadius: 25,
borderWidth: 2,
borderColor: '#0B0B3B'
},
textoboton: {
textAlign: 'center',
color: 'black',
marginBottom: 5,
fontSize: 12
},
})

react-google-maps Customize Marker Icon

Currently, I have rendered the map component into a reusables component successfully using InfoModal to show an icon, however, the standard red Google Map icon I have not modified, and I want to create a custom icon myself. I'm not sure with ES6 and JSX syntax what I need to do. I looked into react-google-maps Issues and attempted to see if there were any current or updated material anywhere for how to do this (which is probably simple), but I'm not sure if react-google-maps has something for custom marker creation in addons or the correct format.
import React, { Component } from 'react'
import { withGoogleMap, GoogleMap, Marker, InfoWindow } from 'react-google-maps'
import { DEFAULT_MARKER } from '../../constants/mapDefaults'
const MapGoogleMap = withGoogleMap(props => (
<GoogleMap
defaultZoom={16}
center={props.center}
>
{props.markers.map((marker, index) => (
<Marker
key={index}
position={marker.position}
onClick={() => props.onMarkerClick(marker)}
>
{marker.showInfo && (
<InfoWindow onCloseClick={() => props.onMarkerClose(marker)}>
<div>{marker.infoContent}</div>
</InfoWindow>
)}
</Marker>
))}
</GoogleMap>
))
export default class Map extends Component {
state = {
center: {
lat: 28.3421135,
lng: -80.6149092
},
markers: [
{
position: new google.maps.LatLng(28.3431165, -80.6135908),
showInfo: false,
infoContent: (
<svg
id="Layer_1"
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 16 16"
>
<path
d="M3.5 0c-1.7 0-3 1.6-3 3.5 0 1.7 1 3 2.3 3.4l-.5 8c0
.6.4 1 1 1h.5c.5 0 1-.4 1-1L4 7C5.5 6.4 6.5 5 6.5
3.4c0-2-1.3-3.5-3-3.5zm10 0l-.8 5h-.6l-.3-5h-.4L11
5H10l-.8-5H9v6.5c0 .3.2.5.5.5h1.3l-.5 8c0 .6.4 1 1 1h.4c.6 0
1-.4 1-1l-.5-8h1.3c.3 0 .5-.2.5-.5V0h-.4z"
/>
</svg>
)
}, DEFAULT_MARKER
]
}
handleMarkerClick = this.handleMarkerClick.bind(this);
handleMarkerClose = this.handleMarkerClose.bind(this);
handleMarkerClick (targetMarker) {
this.setState({
markers: this.state.markers.map(marker => {
if (marker === targetMarker) {
return {
...marker,
showInfo: true
}
}
return marker
})
})
}
handleMarkerClose (targetMarker) {
this.setState({
markers: this.state.markers.map(marker => {
if (marker === targetMarker) {
return {
...marker,
showInfo: false
}
}
return marker
})
})
}
render () {
return (
<MapGoogleMap
containerElement={
<div style={{ height: `500px` }} />
}
mapElement={
<div style={{ height: `500px` }} />
}
center={this.state.center}
markers={this.state.markers}
onMarkerClick={this.handleMarkerClick}
onMarkerClose={this.handleMarkerClose}
/>
)
}
}
this is how I fixed that
let iconMarker = new window.google.maps.MarkerImage(
"https://lh3.googleusercontent.com/bECXZ2YW3j0yIEBVo92ECVqlnlbX9ldYNGrCe0Kr4VGPq-vJ9Xncwvl16uvosukVXPfV=w300",
null, /* size is determined at runtime */
null, /* origin is 0,0 */
null, /* anchor is bottom center of the scaled image */
new window.google.maps.Size(32, 32)
);
return(
<Marker
icon={iconMarker}
key={marker.id}
onClick={onClick}
position={{ lat: parseInt(marker.latitude), lng: parseInt(marker.longitude)}}>
{props.selectedMarker === marker &&
<InfoWindow>
<div style={{'color':'black'}}>
Shop {marker.name} {stretTexte}
</div>
</InfoWindow>}
</Marker>
)
Franklin you can use in react google maps. It has two advantages that takes it to use over info window or simple Marker component. We have Icon options to customize our marker. We have div elements that are fully customizable. You just pass icon url. LabelStyling is optional. Feel free to ask any question.
import { MarkerWithLabel } from 'react-google-maps/lib/components/addons/MarkerWithLabel';
var markerStyling= {
clear: "both", display: "inline-block", backgroundColor: "#00921A", fontWeight: '500',
color: "#FFFFFF", boxShadow: "0 6px 8px 0 rgba(63,63,63,0.11)", borderRadius: "23px",
padding: "8px 16px", whiteSpace: "nowrap", width: "160px", textAlign: "center"
};
<MarkerWithLabel
key={i}
position={marker.position}
labelAnchor={new google.maps.Point(75, 90)}
labelStyle={markerStyling}
icon={{
url: '/build/icon/markPin.svg',
anchor: new google.maps.Point(5, 58),
}}
>
<div>
<p> My Marker </p>
</div>
</MarkerWithLabel>
The Marker spread operator was missing above key={index}. This is the correct code. The icon itself is defined in another file called mapDefaults.js if anyone comes across this issue don't hesitate to reach out to me.
<Marker
{...marker}
key={index}
position={marker.position}
onClick={() => props.onMarkerClick(marker)}
>

React Native - Pass ListView row data to new screen when button clicked?

I'm a newbie using React Native, but using the NativeBase framework I've been able to put an app together for my class project. I have been able to solve most issues with the help of this community as well as the Facebook docs, but I am stuck.
I have a ListView element that I am using to display a list of workout exercises from a JSON file, and that works well as far as I can tell. For now, data being rendered per row includes an image, title, required equipment, and exercise type. The JSON file also contains a YouTube video ID field, but I am not using it just yet because that's where I am stuck.
What I need to do is to open a new screen when someone clicks "Watch Video", and on that screen I want to pass the value from the video field in the JSON file, from that specific row that has been clicked. This should load and play the instructional video from YouTube on the new screen, where I am using the React Native YouTube library to achieve this.
On the same screen, I would also like to reference the information about the exercise that I have mentioned above, so that the user knows they are looking at what they clicked.
I have tried to engineer a solution based on what I have seen here #1, here #2, and here #3, but I have been unsuccessful.
Excuse any mess in the code; I may have forgotten to revert some areas during my trial and error.
Below is one of my Exercise Display screens:
import React, { Component } from 'react';
import { Image, ListView } from "react-native";
import { Container, Content, Header, Button, Icon, Item, Input, Card,
CardItem, Text, Thumbnail, Title, Left, Right, View, Body, Spinner,
ActionSheet, Toast } from 'native-base';
import ActivityIndicator from "react-native-loading-spinner-overlay";
import PTRView from 'react-native-pull-to-refresh';
import styles from "../_overrides/styles";
import exercises from "../../../data/exercises.json";
var BUTTONS = ["30-Minute Cardio", "Bodyweight Workout", "TRX 30
Suspension", "Cancel"];
var CANCEL_INDEX = 3;
class CardioGuides extends Component {
// eslint-disable-line
constructor(props) {
super(props);
this.state = {
// data listing
isLoading: true,
visible: false,
showToast: false,
clicked: '',
searchText: ''
};
}
// pull down to refresh
_refresh() {
return new Promise((resolve) => {
setTimeout(()=>{resolve()}, 2000)
});
}
errorToast(error) {
Toast.show({
text: "Error loading exercise list",
buttonText: "Okay"
})
}
addSuccessToast() {
Toast.show({
text: "Added to " + this.state.clicked,
buttonText: "Okay"
})
}
componentDidMount() {
return fetch("https://activ.raysfitness.co.ke/test/exercises.json")
.then((response) => response.json())
.then((responseJson) => {
let ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
this.setState({
isLoading: false,
dataSource: ds.cloneWithRows(responseJson.resource), // reference to "resource" in the JSON file
}, function() {
// do something with new state
});
})
.catch((error) => {
this.setState({
isLoading: false
});
this.errorToast();
});
}
render() {
// eslint-disable-line
if (this.state.isLoading) {
return (
<ActivityIndicator visible={this.state.isLoading} style={styles.activityIndicator}>
<Spinner color="red" style={styles.activitySpinner} />
</ActivityIndicator>
);
}
return (
<Container style={styles.container}>
<Header searchBar style={styles.header}>
<Item style={styles.searchrow}>
<Button transparent style={styles.searchbtn} onPress={() => this.props.navigation.goBack()}>
<Icon style={styles.searchicon} name="md-arrow-back" />
</Button>
<Input style={styles.searchinput} placeholder="Filter Exercises" value={this.state.searchText}
onChangeText={(searchText) => this.setState({searchText})} placeholderTextColor="#CACACA" returnKeyType="search" />
<Button transparent style={styles.searchbtn}>
<Icon style={styles.searchicon} name="md-search" />
</Button>
</Item>
</Header>
<PTRView onRefresh={this._refresh} >
<Content padder style={{ marginTop: 0 }}>
<ListView
dataSource={this.state.dataSource}
renderRow={(rowData) =>
<Card style={styles.card}>
<CardItem style={{paddingLeft: 6}}>
<Left>
<Thumbnail style={styles.cardthumb} source={{uri: `${rowData.image}`}} />
<Body>
<Text style={styles.cardtitle}>{`${rowData.title}`.toUpperCase()}</Text>
<Text note style={styles.cardnote}>{rowData.equipment} / {rowData.type}</Text>
</Body>
</Left>
</CardItem>
<CardItem style={{ paddingVertical: 0, paddingTop: 0, paddingLeft: 11 }}>
<Left>
<Button iconLeft transparent style={styles.cardbtn}
onPress={() => this._viewExercise(rowData)}>
<Icon active name="md-play" style={styles.cardicon} />
<Text style={styles.cardtext}>Watch Video</Text>
</Button>
</Left>
<Body>
<Button iconLeft transparent style={styles.cardbtn}
onPress={() =>
ActionSheet.show(
{
options: BUTTONS,
cancelButtonIndex: CANCEL_INDEX,
title: "Add to Workout"
},
buttonIndex => {
this.setState({ clicked: BUTTONS[buttonIndex] });
this.addSuccessToast();
}
)}
>
<Icon active name="md-add" style={styles.cardicon} />
<Text style={styles.cardtext}>Add to Workout</Text>
</Button>
</Body>
<Right>
<Button iconLeft transparent style={styles.cardbtn}>
<Icon active name="md-bookmark" style={styles.cardicon} />
<Text style={styles.cardtext}>Save</Text>
</Button>
</Right>
</CardItem>
</Card>
}
>
</ListView>
<View>
<Text style={styles.leadtext}>{'Can\'t find what you\'re looking for? Add it!'}</Text>
<Button block style={styles.ctabtn}
onPress={() => this.props.navigation.navigate("AddExercise")}
>
<Text style={styles.ctatext}>{'Add a Custom Exercise'.toUpperCase()}</Text>
</Button>
</View>
</Content>
</PTRView>
</Container>
);
}
// listen for "watch view" clicks
_viewExercise(rowData) {
this.props.navigation.navigate("ExerciseView");
}
}
export default CardioGuides;
And below is my Exercise View screen:
import React, { Component } from "react";
import { Image, ListView } from "react-native";
import { Container, Header, Title, Content, Text, H3, Button, Icon, Left,
Right, Body, View, Toast } from "native-base";
import YouTube from "react-native-youtube";
import styles from "../_overrides/styles";
var title, type, equipment, video;
const VIDEO_ID = "ZgVjj8JaGf0";
class ExerciseView extends Component {
constructor() {
super();
this.state = {
showToast: false
};
}
errorToast() {
Toast.show({
text: "Could not load exercise",
buttonText: "Okay"
})
}
render(rowData) {
return (
<Container style={styles.container}>
<Header style={styles.header}>
<Left>
<Button transparent onPress={() => this.props.navigation.goBack()}>
<Icon name="md-arrow-back" />
</Button>
</Left>
<Body>
<Title style={styles.title}>{'View Exercise'.toUpperCase()}</Title>
</Body>
<Right />
</Header>
<Content padder style={styles.content}>
<Text style={styles.text}>{"Exercise Detail\n"}</Text>
<View>
<YouTube
apiKey="YOUTUBE-API-KEY-HERE"
videoId={`${VIDEO_ID}`} // The YouTube video ID
play={true} // control playback of video with true/false
fullscreen={false} // control whether the video should play in fullscreen or inline
loop={true} // control whether the video should loop when ended
onReady={e => this.setState({ isReady: true })}
onChangeState={e => this.setState({ status: e.state })}
onChangeQuality={e => this.setState({ quality: e.quality })}
onError={e => this.setState({ error: e.error })}
style={{ alignSelf: 'stretch', height: 200 }}
/>
</View>
</Content>
</Container>
);
}
}
export default ExerciseView;
(Posted on behalf of the OP).
I have been able to solve this with a little direction. For anyone else who might have a similar issue, this is how I did it:
Since I am using react-navigation, I passed the data I needed using navigation in the onPress function, like so:
onPress={() => this.props.navigation.navigate("ExerciseView", { title:
`${rowData.title}`, video: `${rowData.video}` })}
Then accessed the values on the new screen, like so:
render() {
const { params } = this.props.navigation.state;
return (
....
<Text style={styles.text}>{`${params.title} \n`}</Text>
<View>
<YouTube
....
videoId={`${params.video}`} // The YouTube video ID
....
/>
</View>
....
);
}

React-google-maps re-rendering issue

I'm having some issues with the react-google-maps npm module.
First render, works like a charm.
When re-rendering the map-component i get the classic google-maps error.
Uncaught TypeError: Cannot read property 'offsetWidth' of null
Uncaught (in promise) TypeError: Cannot read property 'componentWillReceiveProps' of null(…)
Because the map object isn't available when rendering?
My google-maps Map Component
import React, { PropTypes } from 'react'
import { GoogleMap, Marker, GoogleMapLoader } from 'react-google-maps'
export class Map extends React.Component {
static propTypes = {
coordinates: PropTypes.object,
markers: PropTypes.array
};
render () {
return (<section onloadstyle={{height: '300px'}}>
<GoogleMapLoader
containerElement={
<div
{...this.props}
style={{
height: '300px',
width: '100%'
}}
/>
}
googleMapElement={
<GoogleMap
ref={(map) => console.log(map)}
defaultZoom={15}
defaultCenter={this.props.coordinates}>
{this.props.markers.map((marker, index) => {
return (
<Marker key={index} {...marker} />
)
})}
</GoogleMap>
}
/>
</section>)
}
}
export default Map
And i use the component like this:
var coordinates = {lat: this.props.item.delivery_address_lat, lng: this.props.item.delivery_address_lng}
var map = this.props.item.delivery_address_lat !== 0 && this.props.item.delivery_address_lng !== 0 ? (<Row id='map'>
<Map markers={[{position: coordinates}]} coordinates={coordinates} />
</Row>) : ''
Is this because the google-maps-react module isn't unmounting the component properly or is it something i've done?
The following is inside head
<script src="https://maps.googleapis.com/maps/api/js?key=MY-KEY"></script>
EDIT
Tried to solve it a non react-redux way and this is by no means a solution since it produces the error message: Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op. Please check the code for the Map component.
But still, the map re-renders correctly. I tried doing this the redux way of calling passed prop functions & state and set the {map: section} in the state, passing it down from the calling view. Didn't solve a thing and resulted in the same error message, even though it was delayed with setTimeout
Im kind of stuck here, don't know how to solve this.
componentDidMount () {
this.setState({map: null})
setTimeout(() => this.setState({
map: <section onloadstyle={{height: '300px'}}>
<GoogleMapLoader
containerElement={
<div
{...this.props}
style={{
height: '300px',
width: '100%'
}}
/>
}
googleMapElement={
<GoogleMap
ref={(map) => console.log(map)}
defaultZoom={15}
defaultCenter={this.props.coordinates}>
{this.props.markers.map((marker, index) => {
return (
<Marker key={index} {...marker} />
)
})}
</GoogleMap>
}
/>
</section>
}), 300)
}
render () {
if (!this.state) {
return <span />
} else {
return this.state.map
}
}
The solution for the second edit was to clear the setTimeout() call in the componentWillUnmount() function.
You always have to clear intervals & timeouts when the component is unmounting.
componentDidMount () {
this.setState({map: null})
this.timeout = setTimeout(() => this.setState({
map: <section onloadstyle={{height: '300px'}}>
<GoogleMapLoader
containerElement={
<div
{...this.props}
style={{
height: '300px',
width: '100%'
}}
/>
}
googleMapElement={
<GoogleMap
ref={(map) => console.log(map)}
defaultZoom={15}
defaultCenter={this.props.coordinates}>
{this.props.markers.map((marker, index) => {
return (
<Marker key={index} {...marker} />
)
})}
</GoogleMap>
}
/>
</section>
}), 300)
}
componentWillUnmount () {
clearTimeout(this.timeout)
}
render () {
if (!this.state) {
return <span />
} else {
return this.state.map
}
}
This solution isn't a good one and isn't in-line with the react-redux workflow. But it works.