I am trying to display some long and latitude data on a map when the page is loaded but it seems my page is rendered faster than the data can be fetched.
The data is retrieved properly I have tested the api call.
Can anyone see what I am missing
I tried using useEffect hook but that still does not seem to work:
here is my 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";
function Map() {
const [activites, setActivities] = useState([]);
const [polylines, setPolylines] = useState([]);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
setActivitieData();
setPolylineArray();
setIsLoading(false);
}, []);
const getActivityData = async () => {
const response = await axios.get("http://localhost:8080/api/data");
return response.data;
};
const setActivitieData = async () => {
const activityData = await getActivityData();
setActivities(activityData);
};
const setPolylineArray = () => {
const polylineArray = [];
for (let i = 0; i < activites.length; i ) {
const polylineData = activites[i].map.summary_polyline;
const activityName = activites[i].name;
polylineArray.push({ positions: polyline.decode(polylineData), name: activityName });
}
setPolylines(polylineArray);
console.log("Polyline array = ", polylineArray);
};
return !isLoading ? (
<MapContainer center={[37.229564, -120.047533]} zoom={15} scrollWheelZoom={false} style={{ width: "100%", height: "calc(100vh - 4rem)" }}>
<TileLayer
attribution='© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
{polylines.map((activity, idx) => (
<Polyline key={idx} positions={activity.positions}>
<Popup>
<div>
<h2>{"Name: " activity.name}</h2>
</div>
</Popup>
</Polyline>
))}
</MapContainer>
) : (
<div>
<p>Loading</p>
</div>
);
}
export default Map;
CodePudding user response:
You'll have a better time with
- a data loading library such as
swr
to deal with the state related to getting data from your API (beyond justdata
, you might be interested in e.g.isError
andisValidating
; please refer to the docs), - and
useMemo
for the derived polyline state (because it is always purely derivable from theactivities
you've fetched, and doesn't need to be separately updated state).
Something like this (I'm understandably unable to test this without your API and all, but it should work).
import { MapContainer, Polyline, Popup, TileLayer } from "react-leaflet";
import axios from "axios";
import polyline from "@mapbox/polyline";
import React from "react";
import useSWR from "swr";
function getJSON(url) {
return axios.get(url).then((res) => res.data);
}
function Map() {
const activitySWR = useSWR("http://localhost:8080/api/data", getJSON);
const activities = activitySWR.data ?? null;
const polylines = React.useMemo(() => {
if (!activities) return null; // no data yet
const polylineArray = [];
for (const item of activities) {
const polylineData = item.map.summary_polyline;
polylineArray.push({ positions: polyline.decode(polylineData), name: item.name });
}
return polylineArray;
}, [activities]);
if (!activities) {
return <div>Loading...</div>;
}
return (
<MapContainer
center={[37.229564, -120.047533]}
zoom={15}
scrollWheelZoom={false}
style={{ width: "100%", height: "calc(100vh - 4rem)" }}
>
<TileLayer
attribution='© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
{polylines.map((activity, idx) => (
<Polyline key={idx} positions={activity.positions}>
<Popup>
<div>
<h2>{`Name: ${activity.name}`}</h2>
</div>
</Popup>
</Polyline>
))}
</MapContainer>
);
}
export default Map;
CodePudding user response:
You have two options here.
- Move the logic that depends on the api data to a separate useEffect Hook. So that the logic for setting the polyline array will be calculated only when the api data is loaded.
useEffect(() => {
if(activites.length){
setPolylineArray();
}
setIsLoading(false);
}, [activites])
- Make the useEffect hook async and await the api call using a immediately invoked function. Await the api response and pass the activity data as a parameter since the setActivities function will not have time to complete.
useEffect(() => {
(async () => {
const activities= await getActivityData();
setPolylineArray(activities);
setIsLoading(false);
})();
}, [])
CodePudding user response:
You could try something like this potentially.
This way you can hopefully guarantee the data is available before trying to access it.
useEffect(() => {
setActivitieData().then(() => {
setPolylineArray();
setIsLoading(false);
});
}, []);
Another route would be to pass the data down as well like the following:
Note: I fixed some spelling so this may not copy/paste for you.
useEffect(() => {
setActivitiesData().then((activities) => {
setPolylineArray(activities);
setIsLoading(false);
});
}, []);
const getActivityData = async () => {
const response = await axios.get("http://localhost:8080/api/data");
return response.data;
};
const setActivitiesData = async () => {
const activityData = await getActivityData();
setActivities(activityData);
return activityData
};
const setPolylineArray = (activities) => {
// Do what you want with activities here
};