React Native maps marker change destroys map state - react-native

I am using React Native maps, with real-time marker. This is my code.
const markers = useSelector(state => state.map.markers);
const animate = useCallback(() => {
if (map) {
map.animateToRegion({
latitude: location.latitude,
longitude: location.longitude,
latitudeDelta: location.latitudeDelta,
longitudeDelta: location.longitudeDelta,
});
}
}, [location, map]);
useEffect(() => {
if (location && isMapReady) {
animate();
}
}, [animate, location, isMapReady]);
const onMapLayout = () => {
setIsMapReady(true);
};
<MapView
ref={mapView => {
map = mapView;
}}
provider={PROVIDER_GOOGLE}
region={location}
onPress={() => {
animate();
}}
onMapReady={onMapLayout}>
{isMapReady &&
markers.length !== 0 &&
markers.map((marker, index) => (
<MarkerAnimated
key={index}
coordinate={{
latitude: marker.lat,
longitude: marker.lon,
}}
/>
))}
</MapView>
So whenever the state changes the markers get updated and the marker is changed in real-time in the maps.
But with one issue: the real-time comes in like 10 seconds, within that time, if I try to drag maps to somewhere or zoom in or out of the maps, whenever the marker state changes all these changes will be lost, the zoom will be reset to the old one, and I'll move back to the initial location, etc.
How can I fix this? I want to continue scrolling inside the map, zoom in and out, meanwhile, the markers need to get updated on the app.

This seems very late but if you set the region to initialRegion in your map view it fixes this problem.

Related

React Native Google map and programmatically change pin colors

I built an app with React Native. The app maily shows markers in several locations.
<MapView
style={styles.map}
initialRegion={initialPosition}
showsUserLocation={true}
followsUserLocation={true}
showsCompass={true}
showsPointsOfInterest={false}
customMapStyle={mapStyle}
ref={(current) => (map.current = current)}
>
{
props.data.map((marker: any, index: number) => (
marker.single_point ?
<Marker
key={index}
coordinate={{
latitude: Number(marker.single_point.split(',')[0].trim()),
longitude: Number(marker.single_point.split(',')[1].trim()),
}}
ref={markerRefs[index]}
onPress={() => {
setPin('aqua' === pin ? 'gold' : 'aqua')
markerRefs[index].current.redraw()
}}
pinColor={pin}
>
</Marker>
: null
)
)}
</MapView>
I added a state variable to change the pin color once clicked. The issue here is that the set of coordinates are sent to the map component as a prop.
I did a copy of the prop and add to every object in the array an element/key named pincol
const [propCopy, setPropCopy] = useState([])
setPropCopy( props.data.map(v => ({...v, pincol: 'red'})))
but I'm getting the following error
ERROR Error: Too many re-renders. React limits the number of renders to prevent an infinite loop.
I did this because my idea is to setPropCopy[index].pincol to green from the Marker onPress method.
What am I doing wrong? Thank you
You need to call set in a useEffect to avoid too many re-renders issues like this:
const [propCopy, setPropCopy] = useState([]);
useEffect(() => {
setPropCopy( props.data.map(v => ({...v, pincol: 'red'})))
}, []);

how to loop MapView component API and get a multiple mapRef in Array map method- react native

[enter image description here][1]I am developing Live tracking App for Android and IOS using google maps APIs and the technology i am using is React native.
I am able to list the multiple maps using an array map method but not all the map routes are coming within the frame only the last map is fitting in the frame view, because mapRef is getting overwritten. How do I update mapRef instead of overwritting.
const mapRef = useRef();
useEffect(() => {
screen();
}, [dataSource]);
const screen = () => {
trips = dataSource.map((val, key) => {
let origin = val.route_origin;
let ori = " "+origin;
let destination = val.route_destination;
let dest = " "+destination;
let newDate = val.trip_date;
{console.log("newdate:",newDate)}
var trip_date = moment(newDate).format('DD/MM/YYYY');
let trip_time = val.trip_time;
<MapView
style={styles.maps}
initialRegion={{
latitude: 13.3379,
longitude: 77.1173,
latitudeDelta: 0.0922,
longitudeDelta: 0.0421,
}}
ref={mapRef} // **mapRef is the issue here,it is getting overwritten so fitTocoordinates is updating only for the last mapView**
zoomEnabled={true}
>
<MapView.Marker
pinColor={"green"}
coordinate={{
latitude: 12.9716,
longitude:77.5946,
}}
></MapView.Marker>
<MapViewDirections
origin={ori}
destination={dest}
apikey={"*******************************"} // insert your API Key here
strokeWidth={3}
strokeColor="hotpink"
optimizeWaypoints={true}
onReady={(result) => {
mapRef.current.fitToCoordinates(result.coordinates, {
mapPadding: {
right: Dimensions.get("window").width / 40,
bottom: Dimensions.get("window").height / 70,
left: Dimensions.get("window").width / 40,
top: Dimensions.get("window").height / 70,
},
});
}}
/>
</MapView>
})}
console.log("tripsarr",trips);
settrip1(trips);```
[1]: https://i.stack.imgur.com/5TWr3.png

react-native-maps callout content not updated on change

I am trying to update the content of a <Callout> tag on a google maps view using the react-native-maps npm package. I would like to dynamically load extra content after a map marker is pressed. I have simplified my code to the example below:-
I have already tried using the tracksInfoWindowChanges attribute on the marker, and the showCallout() method to attempt to manually trigger a re-render. I am using android so the iOS only options such as redrawCallout() method are not available.
const MyMap = (props) => {
const [marker, setMarker] = useState({});
return (
<MapView
showsUserLocation
zoomControlEnabled={true}
zoomEnabled={true}
region={{
latitude: 0,
longitude: 0,
latitudeDelta: 0.03,
longitudeDelta: 0.03,
}}>
<Marker
coordinate={{latitude: 0, longitude: 0}}
onPress={(e) => {
setMarker({ title: 'Test'});
}}>
<View>
<Icon name='key' size={40} color='#384963'</Icon>
</View>
<Callout>
<Text>{marker.title}</Text>
</Callout>
</Marker>
</MapView>
);
}
Is it possible to trigger a re-render of the content after the Callout has been rendered once? From debugging I can see that the component renders after I update my marker state, however the content is not visible until I manually close the Callout and re-opened it.
I am having the exact same problem. The suggestion for android, as per this issue, is to use showCallout(). Below is my work-around for this issue using showCallout():
const MyMap = (props) => {
const [marker, setMarker] = useState({});
let markerRef = useRef(null);
useEffect(() => {
if (markerRef.current) {
markerRef.current.showCallout();
}
})
return (
<MapView
showsUserLocation
zoomControlEnabled={true}
zoomEnabled={true}
region={{
latitude: 0,
longitude: 0,
latitudeDelta: 0.03,
longitudeDelta: 0.03,
}}>
<Marker
ref={markerRef}
coordinate={{latitude: 0, longitude: 0}}
onPress={(e) => {
setMarker({title: `Test ${Math.floor(Math.random() * 10)}`});
}}>
<View>
<Icon name='key' size={40} color='#384963' />
</View>
<Callout>
<Text>{marker.title}</Text>
</Callout>
</Marker>
</MapView>
);
}
Briefly, I added a markerRef and call markerRef.current.showCallout() in useEffect(). What this achieves, if I am not mistaken, is that each time MyMap gets re-rendered, the callout will be forced to show again. I also changed the onPress such that the effect of callout re-rendering is more apparent.
Note 1: Each time the marker is pressed, the callout shall have its text updated.
Note 2: If you have multiple markers and callouts, and you want to update only the callout that is currently in view, you will have to add another check in useEffect(). Something like: if (markerRef.current && selectedMarker === 'markerID') to make sure that only the desired callout is re-rendered continuously.

Showing different markers on different points in react native maps

I have a react native app where i'm using react-native-maps.Here, i want to show different marker images for different points according to their coordinate indexes. But its showing just one same marker for all. Here is the code i have now:
{this.state.coordinates.map((coordinate, index) =>
<MapView.Marker key={`coordinate_${index}`}
coordinate={coordinate}
title={coordinate.title}
description={coordinate.description}
image={require(../assests/1.png)}>
</MapView.Marker>
)}
I have more images named 2.png , 3.png etc. How can i show these images in different coordinates such as for index 0 ,it will get 1.png. For index 1,it will get 2.png. What is the wayout from here?
You are hard coding image index to 1, this code should work:
{this.state.coordinates.map((coordinate, index) =>
<MapView.Marker key={`coordinate_${index}`}
coordinate={coordinate}
title={coordinate.title}
description={coordinate.description}
image={require(`../assests/${index}.png`)}>
</MapView.Marker>
)}
Try this code. I hope it solved your problem.
First form the marker array.
For Ex.
var marker1 = {
coordinates: {
latitude: ...,
longitude: ...
},
key: MARKER1,
image: require(../assests/1.png)
};
var marker2 = {
coordinates: {
latitude: ...,
longitude: ...
},
key: MARKER2,
image: require(../assests/2.png)
};
//Push it into your marker array
this.state.markers.push(marker1);
this.state.markers.push(marker2);
//Render
<MapView
...
{this.state.markers.map(marker => (
<MapView.Marker
coordinate={marker.coordinates}
key={marker.key}>
<Image source={marker.image} style={width:50, height:50}/>
</MapView.Marker>
))}
</MapView>

How to show callout in Airbnb maps?

I have a static map with one marker and want to always display the marker callout.
Some folks advise to use MapView's onLayout, but this doesn't work for some time now.
I'm attempting to solve it with onRegionChangeComplete which sometimes works (it gets called once or twice), but in about 10% cases it gets stuck in a loop and gets called again and again until the app crashes. This happens only on iOS.
This is my code
setMarkerRef = (ref) => {
this.marker = ref
}
showCallout = () => {
this.marker.showCallout()
}
render() {
...
<MapView
style={{...StyleSheet.absoluteFillObject}}
region={{
latitude: latitude,
longitude: longitude,
latitudeDelta: 0.00922,
longitudeDelta: 0.00421,
}}
showsMyLocationButton={false}
showsCompass={false}
showsTraffic={false}
zoomEnabled={false}
rotateEnabled={false}
scrollEnabled={false}
pitchEnabled={false}
onRegionChangeComplete={this.showCallout}
onPress={this.openMap}
>
<MapView.Marker
coordinate={{
latitude: latitude,
longitude: longitude,
}}
title={address}
ref={this.setMarkerRef}
/>
</MapView>
Any idea what's wrong with my approach? Does anyone have a better workaround?
Edit: I want the component to look like this, without the need for user interaction. Simply display a map, marker and callout.
Since you're rendering a fixed map with no intended region change, there's no need for you to listen to regionChangeComplete. It's really not a fun event to work with as its behaviour isn't that consistent.
initialRegion would work for showing your map. Or if you would like more fancy animation, trying using MapView animateToRegion.
Anyway, all you need to listen to is componentDidMount.
I suspect your refs code might be wrong, but anyway this works for me.
componentDidMount () {
this.refs['mymarker'].showCallout()
}
render () {
return (
<MapView>
<MapView.Marker
coordinate={YOUR_COORDINATES}
title='my callout'
ref='mymarker'>
</MapView.Marker>
</MapView>
)
}
If you still have problems, it could be that your title prop in MapView.Marker isn't receiving the address you are passing in when showCallout was called. In such a case, the call out doesn't render.
Updated Answer: (seemed like the above wasn't working for Apple Maps)
render () {
return (
<MapView
onLayout={() => this.refs["mymarker"].showCallout()}>
<MapView.Marker
coordinate={YOUR_COORDINATES}
title='my callout'
ref='mymarker'>
</MapView.Marker>
</MapView>
)
}