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>}
Related
I'm still beginner using ReactJS and I'm not understanding a problem I'm having.
My useEffect is getting a list of data and after that I do a filter.
But when I save the data value of this filter, and with a console.log() I try to see the return value of my filter, there is an infinite loop of data saved being loaded and I don't understand why this is happening.
Can anyone help me with this?
Here's my code and it's more easy to understand what I mean:
And I put my code into codesandbox
print of console.log()
import React, { useRef } from "react";
import { Map, TileLayer } from "react-leaflet";
import { getAccommodations } from "./data";
import "./styles.css";
const App = () => {
const center = [-27.592495455704718, -48.484572875610034];
const mapRef = useRef();
const [thing, setThing] = React.useState(0);
const [accommodationData, setAccommodationData] = React.useState([]);
React.useEffect(() => {
if (mapRef.current) {
const response = getAccommodations();
const _response = response.filter((accommodation) => {
return mapRef.current.leafletElement
.getBounds()
.contains([accommodation.listing.lat, accommodation.listing.lng]);
});
setAccommodationData(_response);
}
}, [center, thing, accommodationData]);
// I commented this line to avoid too many re-renders
// console.log("accommodationData: ", accommodationData);
return (
<Map
ref={mapRef}
style={{ width: "100%", height: "100vh" }}
center={center}
zoom={13}
onmoveend={() => {
setThing(thing + 1);
}}
>
<TileLayer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
attribution='© OpenStreetMap contributors'
/>
{/* I'll add some <Markers> here with the accommodationData array */}
</Map>
);
};
export default App;
Thank you very much in advance
The useEffect hook keep trigger because you are changing the state accommodationData which is one of the dependencies of useEffect. So you have just to remove it from dependencies or add another state response and then change accommodationData when you have a response, example :
const center = [-27.592495455704718, -48.484572875610034];
const mapRef = useRef();
const [thing, setThing] = React.useState(0);
const [accommodationData, setAccommodationData] = React.useState([]);
const [response, setResponse] = React.useState(null);
React.useEffect(() => {
if (mapRef.current && !response) {
const response = getAccommodations();
const _response = response.filter((accommodation) => {
return mapRef.current.leafletElement
.getBounds()
.contains([accommodation.listing.lat, accommodation.listing.lng]);
});
setResponse(_response);
}
}, [center, thing, accommodationData]);
React.useEffect(() => {
if (response) setAccommodationData(_response);
}, [response]);
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.
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