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} />
Related
I am slowly learning JS & React, and trying to build an app that pulls weather data from various locations and displays it on a Monday.com dashboard. Still a very long way to go, but putting the puzzle pieces together 1 by 1.
I have a working app that gets the weather based on lattitude & longitude, and then displays it on the page.
Here is my code for App.js
import './App.css';
import React, { useEffect, useState } from "react";
import { Dimmer, Loader } from 'semantic-ui-react';
import Weather from './components/weather';
export default function App() {
const [lat, setLat] = useState([]);
const [long, setLong] = useState([]);
const [data, setData] = useState([]);
// this useEffect will only fire when the component mounts (once)
useEffect(() => {
navigator.geolocation.getCurrentPosition(location => {
setLat(location.coords.latitude);
setLong(location.coords.longitude);
});
}, [])
// this useEffect will be called once the component mounts + when any dependency changes (lat/long)
useEffect(() => {
fetch(`${process.env.REACT_APP_API_URL}/weather/?lat=${lat}&lon=${long}&units=metric&APPID=${process.env.REACT_APP_API_KEY}`)
.then(res => res.json())
.then(result_geo => {
setData(result_geo)
console.log("Latitude is:", lat) // (GL)
console.log("Longitude is:", long) // (GL)
console.log(result_geo);
});
}, [lat, long])
return (
<div className="App">
{(typeof data.main != 'undefined') ? (
<Weather weatherData={data}/>
): (
<div>
<Dimmer active>
<Loader>Loading..</Loader>
</Dimmer>
</div>
)}
</div>
);
}
And here is my code for weather.js
import React from 'react';
import '../styles.css';
import { Card } from 'semantic-ui-react'
import moment from 'moment';
const CardExampleCard1 = ({weatherData}) => (
<div className="Cards">
<Card className="card1">
<Card.Content>
<Card.Header className="header">Location: {weatherData.name}</Card.Header>
<p>{moment().format('dddd')}, {moment().format('LL')} {moment().format('HH:mm')}</p>
<br></br>
<p>Temperature: {weatherData.main.temp} °C</p>
<p>Sunrise: {new Date(weatherData.sys.sunrise * 1000).toLocaleTimeString('en-IN')}</p>
<p>Sunset: {new Date(weatherData.sys.sunset * 1000).toLocaleTimeString('en-IN')}</p>
<p>Description: {weatherData.weather[0].main}</p>
<p>Humidity: {weatherData.main.humidity} %</p>
<br></br>
</Card.Content>
</Card>
</div>
)
export default CardExampleCard1;
The next thing I am trying to figure out is how to display multiple cards (using { Card } from 'semantic-ui-react' ) for various different locations.
I am able to retrieve the weather for an additional location by duplicating the API call and changing the lat&lon (probably there's a better way), and I can store that into a variable, but I don't know how to pass that to weather.js in my return function. I have tried multiple things but it fails. And also how to update my weather.js code to then display different cards.
ANy help would be appreciated - I've been stuck on this for hours.
Eventually, I need to read location names from a monday.com board, do an API call to retrieve their geolocations, then API calls to get their weather, and then display the weather. And this can be for a variable number of locations.
You can use the map function on an array like this below. Assuming the data in state is an array, and the response from server is a JSON object, then when calling for the second time, your array is updating by adding new object in it.
<div>
{data.map(item => <Weather weatherData={item}/>)}
</div>
import './App.css';
import React, { useEffect, useState } from "react";
import { Dimmer, Loader } from 'semantic-ui-react';
import Weather from './components/weather';
export default function App() {
const [lat, setLat] = useState([]);
const [long, setLong] = useState([]);
const [data, setData] = useState([]);
// this useEffect will only fire when the component mounts (once)
useEffect(() => {
navigator.geolocation.getCurrentPosition(location => {
setLat(location.coords.latitude);
setLong(location.coords.longitude);
});
}, [])
// this useEffect will be called once the component mounts + when any dependency changes (lat/long)
useEffect(() => {
fetch(`${process.env.REACT_APP_API_URL}/weather/?lat=${lat}&lon=${long}&units=metric&APPID=${process.env.REACT_APP_API_KEY}`)
.then(res => res.json())
.then(result_geo => {
setData(result_geo)
console.log("Latitude is:", lat) // (GL)
console.log("Longitude is:", long) // (GL)
console.log(result_geo);
});
}, [lat, long])
return (
<div className="App">
{(typeof data.main != 'undefined') ? (
<div>
{data.map(item => <Weather weatherData={item}/>)}
</div>
): (
<div>
<Dimmer active>
<Loader>Loading..</Loader>
</Dimmer>
</div>
)}
</div>
);
}
"display multiple cards": display arrays of Components in React. So to display multiple you can use Array.map().
Ex:
const people = [
{ name: 'Harry' },
{ name: 'Ron' },
{ name: 'Hermione' },
]
...
return (
<>
{people.map((e, idx) => {
return (<span key={idx}>{e.name}</span>)
})}
</>
)
"pass that to weather.js": Not sure to get it. But when your parent component update a state the childrens using that state are going to rerender. So using a setData() when you get your new api response should update weatherData in the child component.
I wanted to prevent losing state on page refresh while being on a different route path. Im curious why the first example does not work. From what i understand when app mounts first thing that gonna render is component itself and then useEffects run. Since i got 3 here, first fetches and saves the data to the invoiceList state and then next useEffect that run should fill localStorage key with invoiceList state data. The last one obviously retrieve the data.
The second one does fill the "invoiceData" localStorage key with an empty array. Why is this happening if the invoiceList state already have the data after the first useEffect?
The second example that i provided works. I removed second useEffect and set localStorage key in the first useEffect with response data that i get from fetch.
I also wonder if im doing everything correct here. Any feedback appreciated :)
First example (not working):
import { ReactElement, useEffect, useState } from "react";
import { Outlet } from "react-router-dom";
import { Bar } from "../../components/Bar/Bar";
import { Invoice } from "./Root.utils";
type Props = {};
const Root = (props: Props): ReactElement => {
const [invoiceList, setInvoiceList] = useState<Invoice[]>([]);
useEffect(() => {
const fetchData = async () => {
const response = await fetch("./data.json");
const data = await response.json();
setInvoiceList(data);
};
fetchData();
}, []);
useEffect(() => {
window.localStorage.setItem("invoiceData", JSON.stringify(invoiceList));
}, []);
useEffect(() => {
setInvoiceList(
JSON.parse(window.localStorage.getItem("invoiceData") || "[]")
);
}, []);
return (
<div>
<Bar />
<Outlet context={{ invoiceList }} />
</div>
);
};
export default Root;
Second example (working):
import { ReactElement, useEffect, useState } from "react";
import { Outlet } from "react-router-dom";
import { Bar } from "../../components/Bar/Bar";
import { Invoice } from "./Root.utils";
type Props = {};
const Root = (props: Props): ReactElement => {
const [invoiceList, setInvoiceList] = useState<Invoice[]>([]);
useEffect(() => {
const fetchData = async () => {
const response = await fetch("./data.json");
const data = await response.json();
window.localStorage.setItem("invoiceData", JSON.stringify(data));
setInvoiceList(data);
};
fetchData();
}, []);
useEffect(() => {
setInvoiceList(
JSON.parse(window.localStorage.getItem("invoiceData") || "[]")
);
}, []);
return (
<div>
<Bar />
<Outlet context={{ invoiceList }} />
</div>
);
};
export default Root;
The first example is never storing the data into the localStorage because the fetch is an asynchronous function that and you are writing basically always the empty array into your localStorage.
The order of execution in the first example will be:
fetchData called
window.localStorage.setItem("invoiceData", JSON.stringify(invoiceList)); <- still empty array
setInvoiceList(JSON.parse(window.localStorage.getItem("invoiceData") || "[]"));
response.json() called
setInvoiceList(data); called
I would also recommend to improve your code a little like that:
import React, { useEffect, useState } from "react";
import { Outlet } from "react-router-dom";
import { Bar } from "../../components/Bar/Bar";
import { Invoice } from "./Root.utils";
const Root: React.FC = () => {
const [invoiceList, setInvoiceList] = useState<Invoice[]>([]);
useEffect(() => {
setInvoiceList(
JSON.parse(window.localStorage.getItem("invoiceData") || "[]")
);
const fetchData = async () => {
const response = await fetch("./data.json");
const data = await response.json();
window.localStorage.setItem("invoiceData", JSON.stringify(data));
setInvoiceList(data);
};
fetchData();
}, []);
return (
<div>
<Bar />
<Outlet context={{ invoiceList }} />
</div>
);
};
export default Root;
You can use the Link component from react-router and specify to={} as an object where you specify pathname as the route to go to. Then add a variable e.g. data to hold the value you want to pass on. See the example below.
Using the <Link /> component:
<Link
to={{
pathname: "/page",
state: data // your data array of objects
}}
>
Using history.push()
this.props.history.push({
pathname: '/page',
state: data // your data array of objects
})
Using either of the above options you can now access data on the location object as per the below in your page component.
render() {
const { state } = this.props.location
return (
// render logic here
)
}
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'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 working on a next.js project and I'm trying to get the list of gyms from getStaticProps like this:
import { MapContainer, TileLayer, Marker, Popup } from "react-leaflet";
import { useState } from "react";
import { server } from '../config';
export default function Map ({gyms}){
console.log("data = "+ JSON.stringify(gyms));
return (
<MapContainer>
<TileLayer url={`I deleted the url`} attribution='Nothing to see here' />
{gyms.map((gym, index) => {
return (
<Marker
key={index}
position={[gym.address1, gym.address2]}
draggable={true}
animate={true}
>
</Marker>
);
})}
</MapContainer>
);
}
export async function getStaticProps() {
const res = await fetch(`${server}/api/gyms`)
const response = await res.json()
const gyms = response.gyms
console.log("gyms list = "+ gyms);
if (!gyms) {
return {
notFound: true,
}
}
return {
props: {
gyms,
// Will be passed to the page component as props
},
};
}
As you can see I have one console.log in the getStaticProps, that returns nothing, and another console log in the component that returns "data = undefined"
The error that I get is: TypeError: Cannot read properties of undefined (reading 'map')
The error you are getting is from the 'gyms' prop. Please make sure if you are using useEffect in the component where you are using Map, the array variable should be in the array. Since you are getting the array as undefined then this is the most possible problem. Just put it in the array like this:
useEffect(() => {
//Code goes here...
}, [gyms])