React Native. Show component next to the marker - google-maps

Is there a way to create a popup similar to the one shown below .
Im using react-native-maps, google provider and markers which are working well, it is only the popup next to the marker that i am having issues with

We can use the Callout component to do this.
The Callout component accepts a custom view and is flexible in what content it accepts:
Callouts to markers can be completely arbitrary react views, similar to markers. As a result, they can be interacted with like any other view.
Source: https://github.com/react-native-community/react-native-maps#custom-callouts
So an example fitted to your use case looks something like this:
const CustomCalloutView = ({ marker }) => (
<View>
<View style={{ flex: 1, flexDirection: "row" }}>
<Text>Kyiv Ukraine</Text>
<Badge value="2" status="warning" />
</View>
<Text>+1°</Text>
</View>
);
// ...
<Marker coordinate={marker.latlng}>
<Callout>
<CustomCalloutView marker={{...marker}} />
</Callout>
</Marker>
For the badge I've used the badge the react-native-elements library provides (https://react-native-elements.github.io/react-native-elements/docs/badge), but you can change this to whatever you want.
To make the CustomCalloutView content dynamic based on the marker coordinates passed as props you could use the function reverseGeocodeAsync from expo-location to get info about the location.
Source: https://docs.expo.io/versions/latest/sdk/location/#locationreversegeocodeasynclocation.
Example using dynamic marker coordinates and expo-location:
import * as Location from "expo-location";
// ...
const CustomCalloutView = ({ marker }) => {
const [location, setLocation] = useState(null);
useEffect(() => {
Location.reverseGeocodeAsync(marker).then(res => {
setLocation(res[0]);
});
}, []);
return (
<View>
{location && (
<View>
<View style={{ flex: 1, flexDirection: "row" }}>
<Text>{`${location.city}, ${location.country}`}</Text>
<Badge value="2" status="warning" />
</View>
<Text>+1°</Text>
</View>
)}
</View>
);
};

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.

Return and render looped text items from XML on React Native in a list

I've found a way to extract items from an XML within ComponentDidMount method and loop them. I also managed to pass them on to the 'render' section of Component and return them as one big wall of strings. But I will need to display them as a list so that one line will be dedicated to one item.
This is my current demo:
https://snack.expo.io/#leourushi/api-looping-00
This is the original XML I’m pulling data from:
https://www.wmata.com/rider_tools/metro_service_status/feeds/mis/rail.xml
And I used this tool to parse XML into readable format:
https://www.npmjs.com/package/react-native-rss-parser
I’m trying to create a list view similar to this one:
https://www.wmata.com/service/status/#lines-affected
I used map function to extract short titles of travel alerts (Yellow Line, Blue Line etc) and more detailed description texts. I stored them into variables named them gimmeNames and gimmeDescriptions respectively.
Now I would like to display them side by side in a list view.
I created empty state names at the top as below.
constructor(props) {
super(props);
this.state = {
rss: [],
gimmeNames: "",
gimmeDescriptions: ""
};
}
Within componentDidMount, I defined the original URL, ran a ‘fetch’ operation and created two loop functions. And I defined the results as gimmeNames and gimmeDescriptions.
componentDidMount() {
var url = 'https://www.wmata.com/rider_tools/metro_service_status/feeds/mis/rail.xml';
return fetch(url)
.then((response) => response.text())
.then((responseData) => rssParser.parse(responseData))
.then((rss) => {
const gimmeNames = rss.items.map(function(item, index) {
return (item.title);
});
const gimmeDescriptions = rss.items.map(function(item, index) {
return (item.description);
});
this.setState({
gimmeNames: gimmeNames,
gimmeDescriptions: gimmeDescriptions,
rss: rss
});
});
}
Here is my attempt to render them side by side. But currently I have one big chunk of ALL the looped title names and another chunk of ALL the description items.
render() {
const { rss, gimmeNames, gimmeDescriptions } = this.state;
return(
<View style={styles.bigContainer}>
<View style={styles.box}>
<Text> Title: Hard coded strings for now </Text>
<View style={styles.lineItem}>
<View style={styles.lineRow}>
<Text style={styles.leftColumn}>{gimmeNames}</Text>
<Text style={styles.rightColumn}>{gimmeDescriptions}</Text>
</View>
</View>
</View>
</View>
)
}
I am definitely doing this wrong. But I don't know how to get to the right answer. Any pointer would be appreciated.
I think this will work. It's similar to other examples that have worked for me before:
constructor(props) {
super(props);
this.state = {
rss: []
};
}
And then when you fetch the data:
componentDidMount() {
var url =
'https://www.wmata.com/rider_tools/metro_service_status/feeds/mis/rail.xml';
return fetch(url)
.then((response) => response.text())
.then((responseData) => rssParser.parse(responseData))
.then((rss) => {
console.log(rss)
this.setState({
rss: rss
});
});
}
And then when you render them:
render() {
const gimmeItems = this.state.rss.items.map((item) =>
<View style={styles.lineItem}>
<View style={styles.lineRow}>
<Text style={styles.leftColumn}>{item.title}</Text>
<Text style={styles.rightColumn}>{item.description}</Text>
</View>
</View>
);
return(
<View style={styles.bigContainer}>
<View style={styles.box}>
<Text> Title: Hard coded strings for now </Text>
{gimmeItems}
</View>
</View>
)
}

How to get a specific item of a Flatlist in React-Native

How can I only render the item in a FlatList if the item.id is the same like the id in the json?
I'm new to react-native and JavaScript so maybe it's a stupid question.
My Code so far:
export default class DetailsScreen extends Component {
constructor(props) {
super(props);
this.state = {
isLoading: true,
dataSource: [],
};
}
componentDidMount() {
this.setState({
isLoading: false,
dataSource: data.data
});
}
render() {
if (this.state.isLoading) {
return (
<View>
<ActivityIndicator />
</View>
);
}
return (
<View>
<FlatList
data={this.state.dataSource}
renderItem={({ item }) => (this.renderDetailView(item))}
keyExtractor={(item, index) => index.toString()}
/>
</View>
);
}
}
That's the simple rendering of the Flatlist and this work quite good.
The 'renderDetailView' is very complex and long so I couldn't add the code.
But it look like this:
renderDetailView(item) {
return (
<View style={styles.info} key={item.id}>
<View>
<Text style={styles.detailView}>
{item.name}
</Text>
<Text>{'\n'}</Text>
</View>
...
</View>
);
}
At the final project i want to handle a click on an other FlatList and show the detail of the clicked item. the clicked Item 'send' the id to this class and so i get the specific detailView.
Sorry for my bad English.
If you want to navigate to a detail page when you tap an item of your list you should use a navigation library (react-navigation is the most popular).
I created a basic working example :
https://snack.expo.io/#sanjar/so-53747271
(In this example I used react-navigation 2 while the last version is react-navigation 3, so use this one too if you want to reproduce it locally)
ps : I'm not sure to fully understand your requirements, you can add a comment if my answer was off topic or if you have any question

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 not showing on screen

i have a react app and i am integrating google map using react-google-maps. It fetches data of lat and lng using node server.
Map.js
import { withGoogleMap, GoogleMap, Marker } from "react-google-maps";
import React from 'react';
// Wrap all `react-google-maps` components with `withGoogleMap` HOC
// and name it GettingStartedGoogleMap
export const GettingStartedGoogleMap = withGoogleMap(props => (
<GoogleMap
ref={props.onMapLoad}
defaultZoom={3}
defaultCenter={props.centre[0]}
onClick={props.onMapClick}>
{props.markers.marker.map((marke, index) => (
<Marker
{...marke}
onRightClick={() => props.onMarkerRightClick(index)}
/>
))}
</GoogleMap>
));
export default GettingStartedGoogleMap;
Layout.js
//some imports
const calculate = (x)=>{
var lt=0;
var lg=0;
var count=0;
x.marker.map((k,i)=>(
lt+=k.position.lat,
lg+=k.position.lng,
count++
))
var centre=[{lat:lt/count, lng:lg/count}];
return centre;
};
export const Layout = props => (
//some div and other elements working fine witout map
<div id="map"><GettingStartedGoogleMap
containerElement={
<div style={{ height: `100%` }} />
}
mapElement={
<div style={{ height: `100%` }} />
}
centre={()=>calculate(props.restraun)}
onMapLoad={_.noop}
onMapClick={_.noop}
markers={props.restraun}
onClick={_.noop}
/></div>
//other elements
);
export default Layout;
Maps is not shown in front end. I have included script tag with api key. Tried changing height from % to fixed value. But still doesn't work.
Data is of format
restran:{
marker:[
{position:
{lat:'value',
lng:'value'
}
key:'value'
}]}
data is being shown correctly if printed on console from node
Edit 1: It seems to be error with the component itself as i was able to print some text instead of markers.