Custom marker not rerendering after coordinates change - reactjs

I'm trying to move some markers around the map but after update the array which contains the info about the coordinates of them nothing happens, it's just blinking but not moving.
BusMarker.js (Customer Marker)
import React, { useState, useEffect } from 'react'
import { Marker } from 'react-native-maps'
const BusMarker = props => {
const { longitude, latitude, plate } = props
const [coordinates] = useState({
longitude: Number(longitude),
latitude: Number(latitude)
})
return (
<Marker
key={`bus-${plate}-${new Date().getMilliseconds()}`}
coordinate={coordinates}
image={require('../../img/icon/bus-icon.png')}>
</Marker>
)
}
export default BusMarker
Map.js
import React, { useState, useEffect, Fragment, useCallback, useRef } from 'react'
import { StyleSheet, View, Text } from 'react-native'
import { useSelector, useDispatch } from 'react-redux'
import MapView, { PROVIDER_GOOGLE} from 'react-native-maps'
import BusMarker from './BusMarker'
import { useFocusEffect } from 'react-navigation-hooks'
const Maps = () => {
const [busLocation, setBusLocation] = useState([
{
"plate": "CAR1203",
"latitude": 0,
"longitude": 0,
},
])
const [region] = useState({
latitude: 0,
longitude: 0,
latitudeDelta: 0.0143,
longitudeDelta: 0.0134,
})
const _renderBusesOnMap = busLocation => {
if (busLocation.length > 0) {
return busLocation.map(({ plate, longitude, latitude }) => {
return (
<BusMarker key={plate} plate={plate} longitude={longitude} latitude={latitude} />
)
})
}
}
const updateLocations = () => {
const newPosition = [
{
"plate": "CAR1203",
"latitude": 0,
"longitude": 1,
},
]
const timeout = setInterval(() => {
setBusLocation(newPosition)
}, 1000)
}
useEffect(updateLocations, [])
return (
<MapView
provider={PROVIDER_GOOGLE}
style={styles.map}
initialRegion={region}
showsCompass={false}
showsTraffic={false}
showsIndoors={false}
showsBuildings={false}
showsPointsOfInterest={false}
loadingEnabled={false}
>
{_renderBusesOnMap(busLocation)}
</MapView>
)
}
const styles = StyleSheet.create({
map: {
flex: 1,
backgroundColor: '#ffff',
height: '100%'
},
})
export default Maps
Why is it not updating its location on the map?
I'm using the following versions on this project:
React Native: 0.60.5
React Native Maps: 0.25.0

Try this instead:
const BusMarker = props => {
const { longitude, latitude, plate } = props;
const coordinates = {
longitude: Number(longitude),
latitude: Number(latitude)
};
return (
<Marker
key={`bus-${plate}-${new Date().getMilliseconds()}`}
coordinate={coordinates}
/>
);
};
const Maps = () => {
const [busLocation, setBusLocation] = useState([
{
plate: "CAR1203",
latitude: 37.78825,
longitude: -122.4324
}
]);
const [region] = useState({
latitude: 37.78825,
longitude: -122.4324,
latitudeDelta: 0.0922,
longitudeDelta: 0.0421
});
const _renderBusesOnMap = busLocation => {
if (busLocation.length > 0) {
return busLocation.map(({ plate, longitude, latitude }) => {
return (
<BusMarker
key={plate}
plate={plate}
longitude={longitude}
latitude={latitude}
/>
);
});
}
};
const updateLocations = () => {
const newPosition = [
{
plate: "CAR1203",
latitude: 37.78845,
longitude: -122.4424
}
];
const timeout = setInterval(() => {
setBusLocation(newPosition);
}, 1000);
};
useEffect(updateLocations, []);
return (
<MapView
provider={PROVIDER_GOOGLE}
style={styles.map}
initialRegion={region}
showsCompass={false}
showsTraffic={false}
showsIndoors={false}
showsBuildings={false}
showsPointsOfInterest={false}
loadingEnabled={false}
>
{_renderBusesOnMap(busLocation)}
</MapView>
);
};
The main problem I think is the way you set coordinates inside BusMarker. There is no need to use useState there. You can just set coordinates as an object with the latitude and longitude values you receive from props.
I've adjusted the coordinates to make it easier to test.
Update
I will try to add to this answer, by explaining my understanding of the behavior. I will explain by giving a more general example. So let's say you have code like this:
const Component = props => {
const [data, setData] = useState(props);
// ...
Now props will be set as the initial value of data.
But useState won't update on props change this way. So even though the props have changed, data still holds the value it was initially passed. To tell data to update on props change you could use useEffect inside Component:
useEffect(() => {
setData(props);
}, [props]);
But like I said in my original answer, there is no reason to use useState in this situation. We don't need to keep Component's state in sync with the parent props as we can simply use props directly. So don't use the useEffect approach I've given as it's unnecessary, it's just to give an example.

Related

Uncaught (in promise) TypeError: mapRef.current.getBounds is not a function in reactjs while using react-google-maps/api library

i am getting error mapRef.current.getBounds is not a function and map.getBounds is not a function when i want to getBounds of map using useRef react hook
this is my code for getting data from the csv file display marker on the map and when user scroll and zoom map then load more data from csv file and display on the marker based on change latitude and longitude of the map but i am getting error
import React, { useState, useEffect, useRef } from "react";
import { GoogleMap, LoadScript, Marker } from "#react-google-maps/api";
import Papa from "papaparse";
const Map = () => {
const [markers, setMarkers] = useState([]);
const mapRef = useRef(null);
const center = {
lat: 49.87099818,
lng: -97.28513314,
};
useEffect(() => {
const fetchData = async () => {
const response = await fetch("/data/geocoords.csv");
const data = await response.text();
const parsedData = Papa.parse(data, { header: true });
const initialMarkers = parsedData.data
.filter((data) => {
const lat = parseFloat(data.latitude);
const lng = parseFloat(data.longitude);
return mapRef.current.getBounds().contains({ lat, lng });
})
.map((data) => ({
position: {
lat: parseFloat(data.latitude),
lng: parseFloat(data.longitude),
},
title: data.title,
address: data.address,
}));
setMarkers(initialMarkers);
};
fetchData();
}, []);
const handleBoundsChanged = () => {
const map = mapRef.current;
const bounds = map.getBounds();
const ne = bounds.getNorthEast();
const sw = bounds.getSouthWest();
const lat1 = ne.lat();
const lng1 = ne.lng();
const lat2 = sw.lat();
const lng2 = sw.lng();
Papa.parse("/data/geocoords.csv", {
download: true,
header: true,
complete: (results) => {
const newMarkers = results.data
.filter((data) => {
const lat = parseFloat(data.latitude);
const lng = parseFloat(data.longitude);
return (
lat >= lat2 &&
lat <= lat1 &&
lng >= lng2 &&
lng <= lng1 &&
!markers.some((marker) => marker.street === data.street)
);
})
.map((data) => ({
position: {
lat: parseFloat(data.latitude),
lng: parseFloat(data.longitude),
},
title: data.street,
}));
setMarkers([...markers, ...newMarkers]);
},
});
};
return (
<LoadScript googleMapsApiKey={"my_api_key"}>
<GoogleMap
ref={mapRef}
onBoundsChanged={handleBoundsChanged}
mapContainerStyle={{ height: "100vh", width: "100%" }}
zoom={10}
center={center}
>
{markers.map((marker, index) => (
<Marker key={index} position={marker.position} title={marker.title} />
))}
</GoogleMap>
</LoadScript>
);
};
export default Map;
this is the screen shot of my error that i'm getting in google chrome console
Please any one can solve my problem i am stuck on this from last two days thanks in advance
Try to check whether the mapRef.current. has the getBounds() method before using it in the useEffect, try this in the useEffect return statement
return mapRef.current?.getBounds()?.contains({ lat, lng });

Real-time Location Functionality is not working. `animateMarkerToCoordinate` function is not Moving to the Specified Coordinates

I'm working on a project and I need to show the real-time location of the user on the map. Below is my code for this functionality but that is not working as I expected. It looks like animateMarkerToCoordinate function is not working properly(not moving the marker to the specified coordinartes) but I don't know surely. Can anyone please help me regarding this? Thank you.
import { StyleSheet, View, Dimensions, Platform } from "react-native";
import React, { useEffect, useState, useRef } from "react";
import MapView, { AnimatedRegion, MarkerAnimated } from "react-native-maps";
import Header from "../components/Header";
import ErrorDialog from "../components/ErrorDialog";
import * as Location from "expo-location";
const { width, height } = Dimensions.get("window");
const ASPECT_RATIO = width / height;
const LATITUDE = 30.3753;
const LONGITUDE = 69.3451;
const LATITUDE_DELTA = 0.005;
const LONGITUDE_DELTA = 0.005;
const LiveLocation = ({ navigation }) => {
const markerRef = useRef(null);
const [error, setError] = useState("");
const [latlng, setLatlng] = useState({ lat: LATITUDE, lng: LONGITUDE });
const [coordinates, setCoordinates] = useState(
new AnimatedRegion({
latitude: LATITUDE,
longitude: LONGITUDE,
latitudeDelta: 0,
longitudeDelta: 0,
})
);
const getMapRegion = () => ({
latitude: latlng.lat,
longitude: latlng.lng,
latitudeDelta: LATITUDE_DELTA,
longitudeDelta: LONGITUDE_DELTA,
});
useEffect(() => {
let unsubscribe;
Location.requestForegroundPermissionsAsync().then((response) => {
if (response.status === "granted") {
Location?.watchPositionAsync(
{
accuracy: Location.Accuracy.BestForNavigation,
timeInterval: 2000,
},
(location_update) => {
const duration = 500;
const { latitude, longitude } = location_update.coords;
const newCoordinates = {
latitude,
longitude,
};
if (Platform.OS === "android") {
if (markerRef) {
markerRef.current.animateMarkerToCoordinate(
newCoordinates,
duration
);
}
}
setLatlng({ lat: latitude, lng: longitude });
}
)
.then((unsub) => {
unsubscribe = unsub;
})
.catch((error) => {
console.log(error.message);
});
} else {
setError("Permission to access location was denied");
}
});
return () => {
unsubscribe.remove();
};
}, []);
return (
<View style={[StyleSheet.absoluteFillObject, styles.container]}>
<MapView
style={[StyleSheet.absoluteFillObject, styles.mapView]}
loadingEnabled
region={getMapRegion()}
>
<MarkerAnimated ref={markerRef} coordinate={coordinates} />
</MapView>
<View style={styles.heading}>
<Header text="Live Location" navigation={() => navigation.goBack()} />
</View>
<View>
<ErrorDialog
visible={!!error}
errorHeader={"Error!"}
errorDescription={error}
clearError={() => {
setError("");
}}
/>
</View>
</View>
);
};
export default LiveLocation;
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "center",
alignContent: "center",
},
mapView: {
flex: 1,
},
heading: { position: "absolute", top: 0, left: 0, right: 0, bottom: 0 },
});

Invalid LngLat object: (NaN, NaN) in react js , "Mapbox"

The project concept is to get geolocation and showing Mapbox map using API...
import ReactDOM from "react-dom";
import React, { useRef, useEffect } from "react";
import { geolocated } from "react-geolocated";
import mapboxgl from "mapbox-gl";
import fetchFakeData from "./api/fetchFakeData";
import Popup from "./components/Popup";
import "./App.css";
mapboxgl.accessToken ="pk.eyJ1IjoiamFja3Nvbi1rYXNpIiwiYSI6ImNrbzdsaDJvNTFvc3Eycm9pdTRxYmRxZjUifQ.BzA0w0U7lP0Ka3FcKkI_1Q";
const App = (props) => {
const mapContainerRef = useRef(null);
const popUpRef = useRef(new mapboxgl.Popup({ offset: 15 }));
// initialize map when component mounts
useEffect(() => {
const map = new mapboxgl.Map({
container: mapContainerRef.current,
// See style options here: https://docs.mapbox.com/api/maps/#styles
style: "mapbox://styles/mapbox/streets-v11",
center: [props.lat, props.long],
zoom: 12.5
});
// add navigation control (zoom buttons)
map.addControl(new mapboxgl.NavigationControl(), "bottom-right");
map.on("load", () => {
// add the data source for new a feature collection with no features
map.addSource("random-points-data", {
type: "geojson",
data: {
type: "FeatureCollection",
features: []
}
});
// now add the layer, and reference the data source above by name
map.addLayer({
id: "random-points-layer",
source: "random-points-data",
type: "symbol",
layout: {
// full list of icons here: https://labs.mapbox.com/maki-icons
"icon-image": "bakery-15", // this will put little croissants on our map
"icon-padding": 0,
"icon-allow-overlap": true
}
});
});
map.on("moveend", async () => {
// get new center coordinates
const { lng, lat } = map.getCenter();
// fetch new data
const results = await fetchFakeData({ longitude: lng, latitude: lat });
// update "random-points-data" source with new data
// all layers that consume the "random-points-data" data source will be updated automatically
map.getSource("random-points-data").setData(results);
});
// change cursor to pointer when user hovers over a clickable feature
map.on("mouseenter", "random-points-layer", (e) => {
if (e.features.length) {
map.getCanvas().style.cursor = "pointer";
}
});
// reset cursor to default when user is no longer hovering over a clickable feature
map.on("mouseleave", "random-points-layer", () => {
map.getCanvas().style.cursor = "";
});
// add popup when user clicks a point
map.on("click", "random-points-layer", (e) => {
if (e.features.length) {
const feature = e.features[0];
// create popup node
const popupNode = document.createElement("div");
ReactDOM.render(<Popup feature={feature} />, popupNode);
// set popup on map
popUpRef.current
.setLngLat(feature.geometry.coordinates)
.setDOMContent(popupNode)
.addTo(map);
}
});
// clean up on unmount
return () => map.remove();
}, []); // eslint-disable-line react-hooks/exhaustive-deps
return <div className="map-container" ref={mapContainerRef} />
};
export default geolocated({
positionOptions: {
enableHighAccuracy: false
},
userDecisionTimeout: 5000
})(App);
class Gps extends React.Component {
constructor() {
super();
this.state = {
latitude: "",
longitude: ""
};
this.getMyLocation = this.getMyLocation.bind(this);
}
componentDidMount() {
this.getMyLocation();
}
getMyLocation() {
const location = window.navigator && window.navigator.geolocation;
if (location) {
location.getCurrentPosition(
(position) => {
this.setState({
latitude: position.coords.latitude,
longitude: position.coords.longitude
});
},
(error) => {
this.setState({
latitude: "err-latitude",
longitude: "err-longitude"
});
}
);
}}
render() {
const { latitude, longitude } = this.state;
return (
<div>
<App lat={latitude} long={longitude} />
</div>
);
}
}
please see this link "codesandbox" : https://codesandbox.io/s/determined-river-7dt0h?file=/src/App.js
props didn't work.
see this lines:
center: [props.lat, props.long]
<App lat={latitude} long={longitude} / >
screenshot
The Problem
The Lat and Lng were initialized to an empty string, but the center property is expecting an array of numbers.
Solution
In Gps component, initialize the lat and long state values to a number as opposed to an empty string
this.state = {
latitude: 38.8951,
longitude: -77.0364
};
In my case, I was using it for web and the issue was causing because of the height of the div in which I was rendering map component. When I set the minHeight for that div, it worked fine for me.

Adding 'onClick' function to a MapContainer from 'react-leaflet' in typescript file

In a Typescript file, I can t import 'Map' from 'react-leaflet' and easily fixed it with 'MapContainer'. However, I need to add an 'onClick' function to it, but 'MapContainer' does not support 'onClick'. I followed the documentation but it led me to new/additional issues... I just need to add a simple onClick function to enable user mark a location with a mouseclick on such map. Anyone knows a simple quick fix?
For those who are still struggling with this, I've just managed to capture that CLICK EVENT IN MAP and (for example, add a marker there).
I'm also adding the geolocation example in case you need it too, so:
Create a functional component that will handle the layer where events will happen (and also that marker get printed in my case).
Instance that func component inside your MapContainer.
import { MapContainer, Marker, TileLayer, useMapEvents } from 'react-leaflet';
const SomeComponent = () => {
const [initialPosition, setInitialPosition] = useState<[number, number]>([0,0]);
const [selectedPosition, setSelectedPosition] = useState<[number, number]>([0,0]);
useEffect(() => {
navigator.geolocation.getCurrentPosition(position => {
const { latitude, longitude } = position.coords;
setInitialPosition([latitude, longitude]);
});
}, []);
...
const Markers = () => {
const map = useMapEvents({
click(e) {
setSelectedPosition([
e.latlng.lat,
e.latlng.lng
]);
},
})
return (
selectedPosition ?
<Marker
key={selectedPosition[0]}
position={selectedPosition}
interactive={false}
/>
: null
)
}
...
return(
<MapContainer
center={selectedPosition || initialPosition}
zoom={12}
>
<Markers />
<TileLayer
attribution='&copy OpenStreetMap contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
</MapContainer>
)
}
I followed the documentation on link and was finally able to make the 'click' event work, and make the 'Marker' render on map. However, it does not point the Marker on selected place on map. It always points the marker on same place on map, and console returns a fixed position(latitude, longitude). I am starting to dislike leaflet.
https://react-leaflet.js.org/docs/example-events/
export default function CreateSomething() {
function LocationMarker() {
const [ position, setPosition ] = useState({ latitude: 0, longitude: 0 })
const map = useMapEvents({
click() {
map.locate()
},
locationfound(e) {
const { lat, lng } = e.latlng;
setPosition({
latitude: lat,
longitude: lng,
})
map.flyTo(e.latlng, map.getZoom())
},
})
return (
position.latitude !== 0 ?
<Marker
position={[position.latitude, position.longitude]}
interactive={false}
icon={happyMapIcon}
/>
: null
)
}
return (
<MapContainer
<LocationMarker />
</MapContainer>
)
}
function AddMarkerToClick() {
const [position, setPosition] = useState({ latitude: 0, longitude: 0 });
const map = useMapEvents({
click(event) {
const { lat, lng } = event.latlng;
setPosition({
latitude: lat,
longitude: lng,
});
},
});
return (
position.latitude !== 0 ? (
<Marker
position={[position.latitude, position.longitude]}
interactive={false}
icon={mapIcon}
/>
) : null
);
}

Google map marker moves wrongly when I holding to press for drag in react native maps

I want to drag a marker in react native mapview.
Drag is ok, but for the first time when I press the marker, it moves a little into above.
I want to fix this unexpected move.
I'm not sure what's the reason is.
You can check the current situations here.
The source code is as follows.
import React, {useState, useEffect} from 'react'
import { View } from 'react-native'
import MapView, {Marker} from 'react-native-maps'
const DEFAULT_DELTA = {latitudeDelta: 0.015, longitudeDelta: 0.0121}
const initialLoc = {
latitude: 24,
longitude: 75
}
const App = () => {
const [location, setLocation] = useState(initialLoc)
useEffect(() => {
navigator.geolocation.watchPosition(
position => {
const {latitude, longitude} = position.coords;
setLocation({
latitude,
longitude
})
},
error => {
console.error(error)
}, {
enableHighAccuracy: true,
timeout: 20000,
maximumAge: 10000
}
);
}, [])
const onMarkPress = (e) => {
const {coordinate} = e.nativeEvent
setLocation(coordinate)
};
const onMapPress = (e) => {
const {coordinate} = e.nativeEvent
setLocation(coordinate)
};
return (
<View style={{flex:1}}>
{location && (
<MapView
style={{flex: 1}}
initialRegion={{
latitude: location.latitude,
longitude: location.longitude,
...DEFAULT_DELTA
}}
onPress={onMapPress}
>
<Marker draggable
coordinate={location}
onDragEnd={onMarkPress}
/>
</MapView>
)}
</View>
)
}
export default App
This behavior is also displayed in the DraggableMarkers.js example of react-native-maps, so your code implementation is not the issue here.
It may have already been reported on the GitHub repo but I haven't been able to find it so I recommend you file a bug for this here.
Hope this helps!

Resources