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.
Related
This question does not show any research effort; it is unclear or not useful
I have created a generic component to be used in 2 cases. 1 case when dealing with single piece of data the other when dealing with an array of data. I am trying to plot this data on a react leaflet map. Right now it works for my landingPage component which deals with the single plots of data. Previously I had it also working for my array of data before I was passing props to generic component to render. The issue is when I try to load the page responsible for displaying the map with the array of data it returns null when the getInitPosition() function is called as the props data seems to be null when component is rendered but not null after it, I checked this through logging to console. I am confused as to how it works in the single component and not the array of data component as the calls to retrieve the data are very similar. Can anyone see where I am going wrong. It seems to be that although my polyineArray is set with correct values I then print out the polylines state to check if it is set after the call to setPolylines(polylineArray) but it seems to be empty and I do not know why? How can I ensure the polylines state is not empty before passing it as props
Map array of data component
import react from "react";
import { useState, useEffect } from "react";
import { MapContainer, TileLayer, Popup, Polyline } from "react-leaflet";
import axios from "axios";
import polyline from "#mapbox/polyline";
import MapComp from "./MapComp";
function Map() {
const [activities, setActivities] = useState([]);
const [polylines, setPolylines] = useState([]);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
setActivitieData();
}, []);
useEffect(() => {
if (activities.length) {
setPolylineArray();
setIsLoading(false);
}
}, [activities]);
const getActivityData = async () => {
const response = await axios.get("http://localhost:8800/api");
return response.data;
};
const setActivitieData = async () => {
const activityData = await getActivityData();
setActivities(activityData);
};
const setPolylineArray = () => {
const polylineArray = [];
for (let i = 0; i < activities.length; i++) {
const polylineData = activities[i].map.summary_polyline;
const activityName = activities[i].name;
const activityType = activities[i].type;
polylineArray.push({
positions: polyline.decode(polylineData),
name: activityName,
activityType: activityType,
});
} // should push activity type as well
setPolylines(polylineArray);
//setIsLoading(false);
console.log("Polyline array = ", polylineArray);
console.log("polylines = ", polylines);
};
return !isLoading ? (
<MapComp activityData={{ polylines }} />
) : (
<div>
<p>Loading...</p>
</div>
);
}
export default Map;
generic map component
import react from "react";
import { MapContainer, TileLayer, Popup, Polyline } from "react-leaflet";
import polyline from "#mapbox/polyline";
import { useEffect, useState } from "react";
function MapComp(props) {
function getInitPosition() {
console.log("props activity data = ", props);
if (!Array.isArray(props.activityData)) {
return [
props.activityData.positions[0][0],
props.activityData.positions[0][1],
];
} else {
return [
props.activityData.poylines.positions[0][0],
props.activityData.poylines.positions[0][1],
];
}
}
return (
<MapContainer center={getInitPosition()} zoom={15} scrollWheelZoom={false}>
<TileLayer
attribution='© OpenStreetMap contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
{!Array.isArray(props.activityData) && (
<Polyline positions={props.activityData.positions}>
<Popup>
<div>
<h2>{"Name: " + +props.activityData.name}</h2>
</div>
</Popup>
</Polyline>
)}
{Array.isArray(props.activityData.polylines) &&
props.activityData.polylines.length > 1 &&
props.activityData.polylines.map((activity, idx) => (
<Polyline key={idx} positions={activity.positions}>
<Popup>
<div>
<h2>{"Name: " + activity.name}</h2>
</div>
</Popup>
</Polyline>
))}
</MapContainer>
);
}
export default MapComp;
try this on you common component
<MapComp activityData={polylines} />
I have a react functional-based component that needs some pieces of information lat and lng to pass into my Autocomplete component. I am trying to get lat and lng from an inner function but i couldnt. I tried doing
{ lat, lng } = getLatLng(results[0])
but it throws some syntax error, so I also tried
lat = getLatLng(results[0].lat)
lng = getLatLng(results[0].lng)
but it doesnt seem to work. This seems like such a simple issue but i cant figure out why. I tried putting the var keyword in front as well but it didnt work, nor did i expect it to. Below is my whole code for the component.
import usePlacesAutocomplete, { getGeocode, getLatLng } from "use-places-autocomplete"
import { useDispatch } from "react-redux";
import { setOrigin, setDestination } from '../../Slices/originDestinationSlice'
import TextField from '#mui/material/TextField';
import Autocomplete from '#mui/material/Autocomplete';
export const StartPlaces = () => {
const dispatch = useDispatch()
const { ready, value, setValue, suggestions: { status, data }, clearSuggestions } = usePlacesAutocomplete();
const handleSelect = async (val, selectedValue) => {
setValue(val, true);
// clearSuggestions();
const results = await getGeocode({ address: val });
// { lat, lng } = getLatLng(results[0])
lat = getLatLng(results[0].lat)
lng = getLatLng(results[0].lng)
}
return <>
<h4>Enter Origin</h4>
<Autocomplete
id="free-solo-demo"
freeSolo
options={data.map(({ description }) => description)}
onChange={(event, value) => {
dispatch(setOrigin({
coordinates: { lat, lng },
name: value
}))
}}
renderInput={(params) => <TextField {...params} label="Origin" onChange={(e) => handleSelect(e.target.value)} placeholder="e.g. Las Vegas" />}
sx={{ width: 300 }}
/>
</>
}
Any help would be greatly appreciated. thank you in advance!!
you should save your lat e lng in a state to use them correctly
try to change your code like this
import usePlacesAutocomplete, { getGeocode, getLatLng } from "use-places-autocomplete"
import React, {useState} from 'react'
import { useDispatch } from "react-redux";
import { setOrigin, setDestination } from '../../Slices/originDestinationSlice'
import TextField from '#mui/material/TextField';
import Autocomplete from '#mui/material/Autocomplete';
export const StartPlaces = () => {
const dispatch = useDispatch()
const { ready, value, setValue, suggestions: { status, data }, clearSuggestions } = usePlacesAutocomplete();
const [lat, setLat] = useState(0)
const [lng, setLng] = useState(0)
const handleSelect = async (val, selectedValue) => {
setValue(val, true);
// clearSuggestions();
const results = await getGeocode({ address: val });
const {lat, lng} = getLatLng(results[0])
setLat(lat)
setLng(lng)
}
return <>
<h4>Enter Origin</h4>
<Autocomplete
id="free-solo-demo"
freeSolo
options={data.map(({ description }) => description)}
onChange={(event, value) => {
dispatch(setOrigin({
coordinates: { lat, lng },
name: value
}))
}}
renderInput={(params) => <TextField {...params} label="Origin" onChange={(e) => handleSelect(e.target.value)} placeholder="e.g. Las Vegas" />}
sx={{ width: 300 }}
/>
</>
}
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'd like to draw simple straight lines between multiple markers on Google Map. I render this map using google-map-react.
Map component
import { useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import GoogleMapReact from 'google-map-react';
import Marker from './marker';
export default function Map() {
const dispatch = useDispatch();
const markers = useSelector(state => state.points);
const [draggable, setDraggable] = useState(true);
const [center, setCenter] = useState({
lat: 59.95,
lng: 30.33
});
const [zoom, setZoom] = useState(14);
const moveMarker = (key, childProps, mouse) => {
let markersCopy = markers;
markersCopy.map(e => e.currPoint === key ? e.currCenter = mouse : e);
dispatch({type: 'REORDER', payload: markersCopy});
}
return (
<div style={{ height: '100vh', width: '100%' }}>
<GoogleMapReact
bootstrapURLKeys={{ key: process.env.REACT_APP_GOOGLE_MAPS_API_KEY }}
defaultCenter={center}
defaultZoom={zoom}
onChange={(e) => dispatch({type: 'CHANGE_CURR_CENTER', payload: e.center})}
draggable={draggable}
onChildMouseDown={() => setDraggable(false)}
onChildMouseUp={() => setDraggable(true)}
onChildMouseMove={(key, childProps, mouse) => moveMarker(key, childProps, mouse)}
>
{markers.map(e => {
return (
<Marker
lat={e.currCenter.lat}
lng={e.currCenter.lng}
text={e.currPoint}
key={e.currPoint}
/>
)
})}
</GoogleMapReact>
</div>
)
}
I'd like to have them connected by straight lines in the order in which they are
on the list (I'm adding them to the list manually in another component). The resulting line should represent the route, the first point in the list is the beginning of the route, the last one is the end of the route. How can I achieve this?
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>}