there are a lot of example on how to use react-native-maps in a Class Component but i haven't found those with Stateless Component. I use Stateless Component to access the a global state. I need to move the camera view of the MapView from the initial region after i got the user's location, After searching in internet i somehow ends up with this code that is not working.
import React, { useState, useEffect, useRef } from 'react'
import MapView, { Marker, PROVIDER_GOOGLE } from 'react-native-maps';
...
const Explore = props => {
const [userCoords, setUserCoords] = useState({
latitude: 0,
longitude: 0
})
const mapRef = useRef(null);
onMapReady = () => {
getUserLocation()
}
getUserLocation = async () => {
Geolocation.getCurrentPosition(
async (position) => {
setUserCoords(position.coords)
mapRef.animateToRegion({
latitude: position.coords.latitude,
longitude: position.coords.longitude,
})
},
async (error) => {
// See error code charts below.
console.log("Geolocation Error" + error.code, error.message);
},
{ enableHighAccuracy: true, timeout: 15000 }
);
}
return(
...
<MapView
ref={mapRef}
onMapReady={() => onMapReady()}
region={{
latitude: userCoords.latitude,
longitude: userCoords.latitude,
latitudeDelta: 0.0922,
longitudeDelta: 0.0421,
showsUserLocation={true}>
...
</MapView>
)
...
export default Explore
Please help me~ Thank you
Instead of using mapRef.animateToRegion you need to use mapRef.current.animateToRegion
Related
I have been stuck on this since 3 days, I am trying to display a map indicating an event location (the address is coming from a form the user would need to fill in)
I am using react Mapbox Gl and Geoapify for reverse Geocoding, any idea why this component is not working? Thank you!
import Image from "next/image"
import {useState, useEffect} from 'react'
import ReactMapGl, { Marker } from "react-map-gl";
import "mapbox-gl/dist/mapbox-gl.css";
export default function EventMap({ evt }) {
const [lat, setLat] = useState(null);
const [lng, setLng] = useState(null);
const [loading, setLoading] = useState(true);
const [viewport, setViewport] = useState({
latitude: 40.712772,
longitude: -73.935242,
width: "100%",
height: "500px",
zoom: 12,
});
useEffect(() => {
const requestOptions = {
method: "GET",
};
fetch(
`https://api.geoapify.com/v1/geocode/search?text=${evt.address}&apiKey=${process.env.NEXT_PUBLIC_GEOAPIFY_API_KEY}`,
requestOptions
)
.then((response) => response.json())
.then((result) => {
const lng = result.features[0].bbox[0];
const lat = result.features[0].bbox[1];
setLat(lat);
setLng(lng);
setViewport({ ...viewport, latitude: lat, longitude: lng });
setLoading(false);
})
.catch((error) => console.log("error", error));
}, []);
if (loading) return false;
return (
<ReactMapGl
{...viewport}
mapboxAccessToken={process.env.NEXT_PUBLIC_MAPBOX_API_TOKEN}
onViewportChange={(vp) => setViewport(vp)}
>
<Marker key={evt.id} latitude={lat} longitude={lng}>
<Image src="/images/pin.svg" width={30} height={30} />
</Marker>
</ReactMapGl>
);
}
I am currently trying to add a leaflet map to nextjs.
With predefined latitude and longitude in the component the display already works.
Now I want to display retrieved data from my api in the component as longitude and latitude.
This does not work because data.latitude is not set until my index page.
Do any of you have an idea how to get data.latitude and data.longitude from my index page into the component?
This is the code of Map component:
import React from "react";
import { TileLayer } from "react-leaflet";
import "leaflet/dist/leaflet.css";
import "leaflet-defaulticon-compatibility/dist/leaflet-defaulticon-compatibility.css";
import StyledMapContainer from "./styled.js";
import { Marker, Popup } from "react-leaflet";
import MarkerIcon from "../mapmarker/index.jsx";
import { data } from "../../pages/index";
console.log(data);
const Map = () => {
return (
<StyledMapContainer
watch
enableHighAccuracy
zoomControl
center={{ lat: data?.longitude, lng: data?.latitude }}
zoom={[13]}
scrollWheelZoom={false}
>
<TileLayer
url="https://api.mapbox.com/styles/v1/mapbox/streets-v11/tiles/256/{z}/{x}/{y}#2x?access_token="
zoomControl={true}
/>
<Marker position={{ lat: data?.longitude, lng: data?.latitude }} icon={MarkerIcon}>
<Popup>The Ship is here!</Popup>
</Marker>
</StyledMapContainer>
);
};
export default Map;
The code from the index is:
const Description = () => {
const { reload, query } = useRouter();
const { nr } = query;
const { name } = query;
const [data, setData] = useState();
const [unixTime, setunixTime] = useState();
const NoSsrMap = dynamic(() => import("../../atoms/map/index"), { ssr: false });
useEffect(async () => {
const { data } = await axios.get(`http://localhost:5000/${nr}`);
const extractedData = data.data;
setData(extractedData);
if (extractedData) {
const unixTimestamp = data.data.unixtime;
const millisecons = unixTimestamp * 1000;
const dateObj = new Date(millisecons);
const humanDateformat = dateObj.toLocaleString();
setunixTime(humanDateformat);
}
}, []);
const MyMap = () => {
return (
<div>
<NoSsrMap />
</div>
);
};
Try this
<StyledMapContainer
watch
enableHighAccuracy
zoomControl
center={{ lat: data?.longitude, lng: data?.latitude }}
zoom={[15]}
scrollWheelZoom={false}
>
I think a part of index.js is missing but I see some improvements for your code right now.
First of all, you shouldn't use useEffect like that in index.js, this is not how useEffect works to do an async function, you can try something like this
useEffect(() => {
const fetchData = async () => {
const { data } = await axios.get(`http://localhost:5000/${nr}`);
// ... do something with data like setData
};
fetchData();
}, []);
This is because the useEffect method should be a normal function.
After that, you could try passing data through props in the Map component, for example
const Map = ({data = {}}) => {
return (
<StyledMapContainer
watch
enableHighAccuracy
zoomControl
center={{ lat: data.longitude, lng: data.latitude }}
zoom={[13]}
scrollWheelZoom={false}
>
<TileLayer
url="https://api.mapbox.com/styles/v1/mapbox/streets-v11/tiles/256/{z}/{x}/{y}#2x?access_token="
zoomControl={true}
/>
<Marker position={{ lat: data.longitude, lng: data.latitude }} icon={MarkerIcon}>
<Popup>The Ship is here!</Popup>
</Marker>
</StyledMapContainer>
);
};
export default Map;
By doing this, your Map component does not depend on the page/index.
When trying to pass props in dynamic import try this
const NoSsrMap = dynamic(() => import("../../atoms/map/index"), { ssr: false });
const MyMap = ({data}) => {
return (
<div>
<NoSsrMap data={data} />
</div>
);
};
And finally use MyMap in your index page
<MyMap data={data}/>
With this your map component should works correctly.
what im trying to do
place a marker on the map and calculate the distance between the marker and a static location on the map.
problem description
previously my useEffect was causing re renders ,now when i try to cache the functions i get this error.
Unhandled Runtime Error
Error: Rendered more hooks than during the previous render line 27
line 27 is where i use useCallback .
placing marker on the map causes effect to run and calculate the distance .
All the options provided to GoogleMap component are outside the functional component .
To avoid re-creating onMapClick function, i'm trying to use UseCallback which leads to this error . same error with useMemo too .
Even if i comment out the onMapClick function and remove it from GoogleMap prop the re rendering issue persists.
import React, { useState, useEffect, useCallback } from 'react';
import { GoogleMap, useLoadScript } from '#react-google-maps/api';
const libraries = ['places'];
const mapContainerStyle = {
width: '100%',
height: '40vh',
};
const center = {
lat: 25.33800452203996,
lng: 55.393221974372864,
};
const options = {
zoomControl: true,
};
const initialMarker = { lat: null, long: null };
export default function index() {
const { isLoaded, loadError } = useLoadScript({
googleMapsApiKey: process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY,
libraries,
});
const [marker, setMarker] = useState(initialMarker);
if (loadError) return 'Error Loading Maps';
if (!isLoaded) return 'Loading Maps';
const onMapClick = useCallback((event) => {
setMarker({
lat: event.latLng.lat(),
lng: event.latLng.lng(),
});
}, []);
useEffect(() => {
if (
google.maps.geometry.spherical.computeDistanceBetween(
new google.maps.LatLng(center.lat, center.lng),
new google.maps.LatLng(marker.lat, marker.lng)
) > 1000
) {
console.log('out of bounds');
}
}, [marker.lat]);
return (
<div>
<GoogleMap
mapContainerStyle={mapContainerStyle}
zoom={14}
options={options}
center={center}
onClick={onMapClick}></GoogleMap>
</div>
);
}
reference from similar re-rendering issue
i tried this solution too , didnt help .
Your problem is this:
if (loadError) return 'Error Loading Maps';
if (!isLoaded) return 'Loading Maps';
const onMapClick = useCallback((event) => {
setMarker({
lat: event.latLng.lat(),
lng: event.latLng.lng(),
});
}, []);
The if conditions above may cancel calling some hooks, hence the warning. You shouldn't use hooks conditionally. You need to refactor your code.
I'm trying to fetch coordinates from a specific city and set them as a state to use as a map provider.
I'm pretty sure it's something stupid, but I've spent more time on this than I want to admit.
It sorta works, I can set the state and I can console.log the coords, however, it's first null, null. Then lat, null, and at last, lat long. Like this.
null null
101.6942371 null
101.6942371 3.1516964
I want to set the last two coordinates as state and I think it works as it should, but, I want to use this state as lat and long, like this:
latitude: lat,
longitude: long,
When I do this, I get "Error: longitude must be supplied". I believe it's because it tries to set null, null as coordinates.
The code is as follows:
import React, { useState, useEffect } from "react";
import MapGL, { GeolocateControl } from "react-map-gl";
import "mapbox-gl/dist/mapbox-gl.css";
import { OpenStreetMapProvider } from "leaflet-geosearch";
const Map = (props) => {
const [long, setLong] = useState(null);
const [lat, setLat] = useState(null);
const styles = {
width: "100%",
height: "85%",
position: "absolute",
};
useEffect(() => {
const provider = new OpenStreetMapProvider();
const fetchData = async () => {
const results = await provider.search({ query: props.currentCity });
setLong(results[0].x);
setLat(results[0].y);
};
console.log(long, lat)
fetchData();
}, [long, lat]);
const TOKEN =
"xxx";
const [viewport, setViewPort] = useState({
width: "75%",
height: 400,
latitude: lat,
longitude: long,
zoom: 12,
});
const _onViewportChange = (viewport) => {
setViewPort({ ...viewport, transitionDuration: 3000 });
};
return (
<div style={{ margin: "0 auto" }}>
<MapGL
{...viewport}
style={styles}
mapboxApiAccessToken={TOKEN}
mapStyle="mapbox://styles/mapbox/streets-v9"
onViewportChange={_onViewportChange}
></MapGL>
</div>
);
};
export default Map;
I'll just use the component as .
Thanks
Solved it.
Gave up and went with a mix of mapboxgl and openstreetmapprovider.
https://docs.mapbox.com/help/tutorials/use-mapbox-gl-js-with-react/
Pretty dumb I didn't do that earlier.
If you do not wish to fix the first 2 states of your "provider.search()" async function, this might work:
const fetchData = async () => {
const results = await provider.search({ query: props.currentCity });
if (results[0].x && results[0].y) {
setLong(results[0].x);
setLat(results[0].y);
setViewPort({...viewport,
latitude: results[0].y,
longitude: results[0].x,});
}
};
Additionally you could do:
{viewport.latitude && viewport.longitude && <MapGL
{...viewport}
style={styles}
mapboxApiAccessToken={TOKEN}
mapStyle="mapbox://styles/mapbox/streets-v9"
onViewportChange={_onViewportChange}
></MapGL>}
The question may seem a little vague, I'm new using hooks, I'll be quite specific in my example, I have 3 variables, with their setter, and a useEffect that works on them. The code basically asks the user for location permissions and saves his position.
This piece of code is reused exactly the same in two different screens, my question is, to what extent it is feasible to move all the code variables and setters, and use effect to a third file "helper".
Here is the piece of code:
const [localitzacioActual, setlocalitzacioActual] = useState(null);
const [localitzacioPermisos, setlocalitzacioPermisos] = useState(null);
const [mapRegion, setMapRegion] = useState(null);
useEffect( () => {
const demanarPermisos = async () => {
let { status } = await Permissions.askAsync(Permissions.LOCATION);
if (status !== 'granted') {
setlocalitzacioPermisos('Permisos denegats')
} else {
setlocalitzacioPermisos(true)
}
let location = await Location.getCurrentPositionAsync({});
setlocalitzacioActual(JSON.stringify(location))
setMapRegion({ latitude: location.coords.latitude, longitude: location.coords.longitude, latitudeDelta: 0.0022, longitudeDelta: 0.0121 });
}
demanarPermisos()
}, []);
To what point I can instantiate this code to another file, y still need to declare the constants, and the use effect but I can move all the login to a third function outside of the file?
Thanks!
You can put all of your state variables and the function in a custom hook. Your custom hook will handle the state changes for you.
permisos.js
import { useState } from 'react';
const usePermisos= () => {
const [localitzacioActual, setlocalitzacioActual] = useState(null);
const [localitzacioPermisos, setlocalitzacioPermisos] = useState(null);
const [mapRegion, setMapRegion] = useState(null);
const demanarPermisos = async () => {
let { status } = await Permissions.askAsync(Permissions.LOCATION);
if (status !== 'granted') {
setlocalitzacioPermisos('Permisos denegats')
} else {
setlocalitzacioPermisos(true)
}
let location = await Location.getCurrentPositionAsync({});
setlocalitzacioActual(JSON.stringify(location))
setMapRegion({ latitude: location.coords.latitude, longitude: location.coords.longitude, latitudeDelta: 0.0022, longitudeDelta: 0.0121 });
};
return [
localitzacioActual,
localitzacioPermisos,
mapRegion,
demanarPermisos,
];
};
export default usePermisos;
Then import them wherever you need them. You still have to use useEffect to fire off your function.
screen1.js
import React, { useEffect } from 'react';
import usePermisos from './usePermisos';
const screen1 = () => {
const [
localitzacioActual,
localitzacioPermisos,
mapRegion,
demanarPermisos,
] = usePermisos();
useEffect(demanarPermisos, []);
return (
<div>React Functional Component</div>
);
};
export default screen1;
If you need your setters outside of demanarPermisos you can return them from usePermisos.
Well, I'll answer my own question. For anyone wondering the same thing:
Yes, it is possible to move all the code out to a third function. Just add a return with all the variables you need in the screen:
LocalitzacioHelper.js
import React, {useState, useEffect} from 'react';
import * as Location from 'expo-location';
import * as Permissions from 'expo-permissions';
export const demanarLocalitzacio = () => {
const [localitzacioActual, setlocalitzacioActual] = useState(null);
const [localitzacioPermisos, setlocalitzacioPermisos] = useState(null);
const [mapRegion, setMapRegion] = useState(null);
useEffect( () => {
const demanarPermisos = async () => {
let { status } = await Permissions.askAsync(Permissions.LOCATION);
if (status !== 'granted') {
setlocalitzacioPermisos('Permisos denegats')
} else {
setlocalitzacioPermisos(true)
}
let location = await Location.getCurrentPositionAsync({});
setlocalitzacioActual(JSON.stringify(location))
setMapRegion({ latitude: location.coords.latitude, longitude: location.coords.longitude, latitudeDelta: 0.0022, longitudeDelta: 0.0121 });
}
demanarPermisos()
}, []);
return [localitzacioActual, localitzacioPermisos, mapRegion]
}
Then in the screen you just call the function before the return:
MapaScreen.js
const [localitzacioActual, localitzacioPermisos, mapRegion] = demanarLocalitzacio()
The use effect will have the exact same behavior as it was directly inside de screen render function.