How can I access array built in useEffect hook with React? - javascript

I'm using useEffect to make 2 requests to 2 different API's. I'm building an array based on the info that's getting returned. I'd like to access this array outside of useEffect and use it in the return below, where I want to use the data to render points on a map. When I try to access it, like how I'm using parkData it says all_data is not defined.
import React, {useEffect} from "react";
import {MapContainer, Marker, TileLayer } from "react-leaflet";
import * as parkData from "./data/skateboard-parks.json";
import "./App.css";
import axios from 'axios';
let all_info = []
export default function App() {
const validator_url = "http://api-1.com"
const ip_url = "http://ip-api.com/json/"
useEffect(() => {
async function fetchData() {
const result1 = await axios.get(validator_url);
for (let i = 0; i < result1.data.count; i+=1) {
const result2 = await axios.get(`${ip_url}${result1.data.results[i].ip_address}`);
let ip_address = result1.data.results[i].ip_address
let lat = result2.data.lat
let lon = result2.data.lon
all_info.push([ip_address, lat, lon])
}
}
fetchData();
}, []);
return (
<MapContainer center={[45.4, -75.7]} zoom={12}>
<TileLayer
attribution='© OpenStreetMap contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
{/* // HOW CAN I ACCESS all_info ARRAY HERE instead of using parkData? */}
{parkData.features.map(park => (
<Marker key={park.properties.PARK_ID} position={[park.geometry.coordinates[1], park.geometry.coordinates[0]]}>
</Marker>
))}
</MapContainer>
);
}

You will have to store that data in the internal state of the component instead of the global variable.
import React, {useEffect, useState} from "react";
import {MapContainer, Marker, TileLayer } from "react-leaflet";
import * as parkData from "./data/skateboard-parks.json";
import "./App.css";
import axios from 'axios';
export default function App() {
const [allInfo, setAllInfo] = useState([]);
const validator_url = "http://api-1.com"
const ip_url = "http://ip-api.com/json/"
useEffect(() => {
async function fetchData() {
const result1 = await axios.get(validator_url);
const tempData = []
for (let i = 0; i < result1.data.count; i+=1) {
const result2 = await axios.get(`${ip_url}${result1.data.results[i].ip_address}`);
let ip_address = result1.data.results[i].ip_address
let lat = result2.data.lat
let lon = result2.data.lon
tempData.push([ip_address, lat, lon])
}
setAllInfo(tempData);
}
fetchData();
}, []);
return (
<MapContainer center={[45.4, -75.7]} zoom={12}>
<TileLayer
attribution='© OpenStreetMap contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
{/* // HOW CAN I ACCESS all_info ARRAY HERE instead of using parkData? */}
{parkData.features.map(park => (
<Marker key={park.properties.PARK_ID} position={[park.geometry.coordinates[1], park.geometry.coordinates[0]]}>
</Marker>
))}
</MapContainer>
);
}

Try below:
{all_info?all_info.features.map(all_info => (
// your code
)):null}

Create a state using useState hook to store the array and use that rather than defining the array outside the function.
export default function App() {
const [allInfo, setAllInfo] = React.useState([]);
const validator_url = "http://api-1.com"
const ip_url = "http://ip-api.com/json/"
useEffect(() => {
async function fetchData() {
const result1 = await axios.get(validator_url);
const data = [];
for (let i = 0; i < result1.data.count; i+=1) {
const result2 = await axios.get(`${ip_url}${result1.data.results[i].ip_address}`);
let ip_address = result1.data.results[i].ip_address
let lat = result2.data.lat
let lon = result2.data.lon
data.push([ip_address, lat, lon]);
}
setAllData(prevState => [...prevState, data]);
}
fetchData();
}, []);
...
}

Related

props data null when component is rendered

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} />

Why am I having too many re-renders in my React.useEffect()?

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 get data from Api but when I try to set the value with useState it give me an empty object

Hello everyone newbie here! I'm trying to use UseState hook to set setForecastData to the result that I receive from the API call, so then I will be able to access to it and use it elsewhere.
After I set a new value to the hook I try to console.log it and it still give me an empty object!
I don't know what Im doing wrong in here ? any suggestion is appreciate thanks!
( please see comments on code )
import axios from "axios";
import "./App.css";
import HomePage from "./Components/HomePage";
import MainPage from "./Components/MainPage";
const App = () => {
const apiKey = process.env.REACT_APP_API_KEY;
const [input, setInput] = useState("");
const [city, setCity] = useState("");
const [matchesArray, setMatchesArray] = useState([]);
const [locationData, setLocationData] = useState();
const [forecastData, setForecastData] = useState({});
//get today weather info
const getCurrentWeather = () => {
axios
.get(
`https://api.openweathermap.org/data/2.5/weather?q=${city}&units=metric&APPID=${apiKey}`
)
.then((res) => {
console.log(res.data);
let resp = res.data;
setLocationData(resp);
})
.catch((err) => console.log(err));
};
//get weekly weather info
const getWeeklyWeather = () => {
let lat = matchesArray[0].lat;
let lon = matchesArray[0].long;
axios
.get(
`https://api.openweathermap.org/data/2.5/onecall?lat=${lat}&lon=${lon}&exclude=minutely,current&units=metric&appid=${apiKey}`
)
.then((res) => {
const utcOffset = res.data.timezone_offset;
const hourly = res.data.hourly;
const daily = res.data.daily;
let hourlyReduced = hourly.map((hour, index) => ({
id: index,
temp: hour.temp,
weatherCondition: hour.weather[0].main,
weatherIcon: hour.weather[0].icon,
}));
hourlyReduced = hourlyReduced.slice(0, 24);
const dailyReduced = daily.map((day, index) => ({
id: index,
minTemp: day.temp.min,
maxTemp: day.temp.max,
weatherCondition: day.weather[0].main,
weatherIcon: day.weather[0].icon,
}));
const forecastInfo = {
utcOffset: utcOffset,
hourlyForecast: hourlyReduced,
dailyForecast: dailyReduced,
};
setForecastData(forecastInfo); // NOT WORKING
console.log(forecastData); // this is not working! it show me an empty object
console.log(hourlyReduced); // this work fine and it show the result
console.log(dailyReduced); // this work fine and it show the result
return forecastInfo;
});
};
return (
<div className="App">
<HomePage
input={input}
setInput={setInput}
city={city}
setCity={setCity}
matchesArray={matchesArray}
getCurrentWeather={getCurrentWeather}
setMatchesArray={setMatchesArray}
getWeeklyWeather={getWeeklyWeather}
locationData={locationData}
forecastData={forecastData}
/>
<MainPage locationData={locationData} forecastData={forecastData} />
</div>
);
};
export default App;
useState hooks are asynchronous. Logging forecastData right after calling setForecastData will not guarantee that the hook has finished updating the state. Use a useEffect hook to log forecastData whenever it changes.
useEffect(() => {
console.log(forecastData)
}, [forecastData])

useEffect has a setState in it and is console.logging null

I will try to word this in the best way I can...
When I send a function through a prop to a child and then send it again to another child, then use the on click to activate it in the 'grandparent' function. When I console.log in that original function in the grandparent that console.logs a state, it prints undefined, yet when I am within that grandparent and try to activate that function, it will log the state correctly. If anyone can help me a little bit more in depth that would be great, we can call!
import React, { useEffect } from 'react';
import Row from '../row/row';
import './body.css';
import { nanoid } from 'nanoid';
export default function Body () {
const [boxMain, setBoxMain] = React.useState(null)
const [rows, setRows] = React.useState(null)
const ref = React.useRef(null)
function changeBox (event) {
console.log(event);
console.log(boxMain);
}
React.useEffect(() => {
/* Describes array with all information */
const sideBoxes = 40;
const heightContainer = ref.current.offsetHeight
const widthContainer = ref.current.offsetWidth;
const numRows = Math.floor(heightContainer / sideBoxes) - 1
const numBoxes = Math.floor(widthContainer / sideBoxes)
/* Beginning of array birth */
let main = Array(numRows).fill().map(() => new Array(numBoxes).fill({
id: "",
water: false,
land: false,
air: false,
}));
/* End of array birth */
const rows = []
for (let i = 0; i < numRows; i++) {
const id = nanoid();
rows.push(
<Row
key={id}
id={id}
rowNumber={i}
numBoxes={numBoxes}
sideBoxes={sideBoxes}
changeBox={changeBox}
main={main}
/>
)
}
setRows(rows)
setBoxMain(main)
}, [])
return (
<div>
<div onClick={() => changeBox("test")}>
TEST
</div>
<div ref={ref} className='body'>
{rows}
</div>
</div>
)
}
For examples here onClick={() => changeBox("test") the function works and logs "boxMain" correctly. But when I pass changeBox={changeBox} into ...
import React, { useEffect } from "react";
import Box from "../box/box";
import "./row.css";
import { nanoid } from 'nanoid';
export default function Row (props) {
const ref = React.useRef(null)
const [boxes, setBoxes] = React.useState()
useEffect(() => {
const tempBoxes = []
for (let i = 0; i < props.numBoxes; i++) {
const id = nanoid()
tempBoxes.push(
<Box
rowNumber={props.rowNumber}
columnNumber={i}
key={id}
id={id}
side={props.sideBoxes}
changeBox={props.changeBox}
main={props.main}
/>
)
}
setBoxes(tempBoxes)
}, [])
return (
<div ref={ref} className="row-main">
{boxes}
</div>
)
}
Then pass changeBox={props.changeBox} to ...
import React from "react";
import "./box.css";
export default function Box (props) {
React.useEffect(() => {
props.main[props.rowNumber][props.columnNumber] = props.id
}, [])
const [detectChange, setDetectChange] = React.useState(0)
const ref = React.useRef(null)
const styles = {
width: `${props.side}px`,
height: `${props.side}px`,
}
return (
<div
ref={ref}
className="box-main"
key={props.id}
id={props.id}
rownumber={props.rowNumber}
columnnumber={props.columnNumber}
style={styles}
onClick={() => props.changeBox([props.id, props.rowNumber, props.columnNumber])}
>
</div>
)
}
I then have the onClick={() => props.changeBox([props.id, props.rowNumber, props.columnNumber])} and it returns to the original changeBox...
function changeBox (event) {
console.log(event);
console.log(boxMain);
}
but when I click the box it returns the event correctly but returns boxMain as null.
When I click the onClick in the parent function although it console.logs everything correctly.
I know this is a ton of info but I know the fix has to be simple, or at least my method to do this should change.
Thank you for any feedback!! :)
Edit 1:
This is the output normally.
But when I simply add a space to the code and save it in VS Code (I guess some type of rerendering happens?) then it fixes to...
Although the IDs do change so I think everything refreshes in some way.
The useEffect hook of Body component runs only once because it does not have any dependency, thus changeBox callback passed to its children and grand children has the default state of boxMain, and it never updates.
This is why calling changeBox inside Body component logs boxMain array correctly, while calling props.changeBox inside children components logs null.
-------------- Solution ---------------------
This is not the BEST solution, but it will give you an idea why it didn't work before, and how you can fix it.
import React, { useEffect } from 'react';
import Row from '../row/row';
import './body.css';
import { nanoid } from 'nanoid';
export default function Body () {
const [boxMain, setBoxMain] = React.useState(null)
const [rows, setRows] = React.useState(null)
const [rowsData, setRowsData] = React.useState(null)
const ref = React.useRef(null)
function changeBox (event) {
console.log(event);
console.log(boxMain);
}
React.useEffect(() => {
/* Describes array with all information */
const sideBoxes = 40;
const heightContainer = ref.current.offsetHeight
const widthContainer = ref.current.offsetWidth;
const numRows = Math.floor(heightContainer / sideBoxes) - 1
const numBoxes = Math.floor(widthContainer / sideBoxes)
/* Beginning of array birth */
let main = Array(numRows).fill().map(() => new Array(numBoxes).fill({
id: "",
water: false,
land: false,
air: false,
}));
/* End of array birth */
const rowsData = []
for (let i = 0; i < numRows; i++) {
const id = nanoid();
rowsData.push({
key: id,
id,
rowNumber: id,
numBoxes,
sideBoxes,
})
}
setRowsData(rowsData)
setBoxMain(main)
}, [])
React.useEffect(() => {
const rows = []
for (let i = 0; i < rowsData?.length; i++) {
const id = nanoid();
const data = rowsData[i];
rows.push(
<Row
{...data}
changeBox={changeBox}
main={boxMain}
/>
)
}
setRows(rows)
}, [rowsData, boxMain, changeBox])
return (
<div>
<div onClick={() => changeBox("test")}>
TEST
</div>
<div ref={ref} className='body'>
{rows}
</div>
</div>
)
}

Nextjs Fetch Data props to components

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.

Categories

Resources