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

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]);

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

can't clean up setTimeout in useEffect unmount cleanup

I am trying to make kind of flash message displayer to display success, error, warning messages at the top for a certain duration.
I have made the use of useRef hook to store timeouts so that I can clear it incase component unmounts before timeout completion.
Everything works as expected except, if the component unmounts before timeout callback, it does not clear the timeout which indeed is trying to setState which gives
Warning: Can't perform a React state update on an unmounted component
import React, { useEffect, useRef, useState } from 'react'
import SuccessGreen from '../../assets/SuccessGreen.svg'
import Cross from '../../assets/Cancel.svg'
import WarningExclamation from '../../assets/WarningExclamation.svg'
const ICONS_MAP = {
"warning": WarningExclamation,
"success": SuccessGreen,
"error": ""
}
export const FlashMessages = ({
duration=5000,
closeCallback,
pauseOnHover=false,
messageTheme='warning',
typoGraphy={className: 'text_body'},
firstIcon=true,
...props
}) => {
const [isDisplayable, setIsDisplayable] = useState(true)
const resumedAt = useRef(null)
const remainingDuration = useRef(duration)
const countDownTimer = useRef(null)
useEffect(() => {
countDownTimer.current = resumeDuration()
console.log(countDownTimer, "From mount")
return () => {clearTimeout(countDownTimer.current)}
}, [])
const resumeDuration = () => {
clearTimeout(countDownTimer.current)
resumedAt.current = new Date()
return setTimeout(() => forceCancel(), remainingDuration.current)
}
const pauseDuration = () => {
if(pauseOnHover){
clearTimeout(countDownTimer.current)
remainingDuration.current = remainingDuration.current - (new Date() - resumedAt.current)
}
}
const forceCancel = () => {
console.log(countDownTimer, "From force")
clearTimeout(countDownTimer.current);
setIsDisplayable(false);
closeCallback(null);
}
return isDisplayable ? (
<div onMouseEnter={pauseDuration} onMouseLeave={resumeDuration}
className={`flash_message_container ${messageTheme} ${typoGraphy.className}`} style={props.style}>
{ firstIcon ? (<img src={ICONS_MAP[messageTheme]} style={{marginRight: 8, width: 20}} />) : null }
<div style={{marginRight: 8}}>{props.children}</div>
<img src={Cross} onClick={forceCancel} style={{cursor: 'pointer', width: 20}}/>
</div>
):null
}
I have tried to mimic the core functionality of this npm package
https://github.com/danielsneijers/react-flash-message/blob/master/src/index.jsx
but whith functional component.
I think the problem is that when the mouseleave event happens, the timeout id returned by resumeDuration is not saved in countDownTimer.current, so the timeout isn't cleared in the cleanup function returned by useEffect.
You could modify resumeDuration to save the timeout id to countDownTimer.current instead of returning it:
countDownTimer.current = setTimeout(() => forceCancel(), remainingDuration.current)
and then, inside useEffect, just call resumeDuration, so the component would look like this:
import React, { useEffect, useRef, useState } from 'react'
import SuccessGreen from '../../assets/SuccessGreen.svg'
import Cross from '../../assets/Cancel.svg'
import WarningExclamation from '../../assets/WarningExclamation.svg'
const ICONS_MAP = {
"warning": WarningExclamation,
"success": SuccessGreen,
"error": ""
}
export const FlashMessages = ({
duration=5000,
closeCallback,
pauseOnHover=false,
messageTheme='warning',
typoGraphy={className: 'text_body'},
firstIcon=true,
...props
}) => {
const [isDisplayable, setIsDisplayable] = useState(true)
const resumedAt = useRef(null)
const remainingDuration = useRef(duration)
const countDownTimer = useRef(null)
useEffect(() => {
resumeDuration()
console.log(countDownTimer, "From mount")
return () => {clearTimeout(countDownTimer.current)}
}, [])
const resumeDuration = () => {
clearTimeout(countDownTimer.current)
resumedAt.current = new Date()
countDownTimer.current = setTimeout(() => forceCancel(), remainingDuration.current)
}
const pauseDuration = () => {
if(pauseOnHover){
clearTimeout(countDownTimer.current)
remainingDuration.current = remainingDuration.current - (new Date() - resumedAt.current)
}
}
const forceCancel = () => {
console.log(countDownTimer, "From force")
clearTimeout(countDownTimer.current);
setIsDisplayable(false);
closeCallback(null);
}
return isDisplayable ? (
<div onMouseEnter={pauseDuration} onMouseLeave={resumeDuration}
className={`flash_message_container ${messageTheme} ${typoGraphy.className}`} style={props.style}>
{ firstIcon ? (<img src={ICONS_MAP[messageTheme]} style={{marginRight: 8, width: 20}} />) : null }
<div style={{marginRight: 8}}>{props.children}</div>
<img src={Cross} onClick={forceCancel} style={{cursor: 'pointer', width: 20}}/>
</div>
):null
}
and it would then mimic the logic from https://github.com/danielsneijers/react-flash-message/blob/master/src/index.jsx

Drawing lines between Markers using Google-Map-React

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?

React Native application freezes while fetching API

Hi I have very common problem, react native application freezes while fetching API,
It is like when you fetching you cannot click or do anythink till your fetching ends.
This is my code where I call function which fetchs APi
import React, { useEffect, useState } from "react";
import { StyleSheet, View, FlatList } from "react-native";
// Api`s
import { topCategory } from "../../api/appModels/category";
import { banners } from "../../api/appModels/slider";
import { dinamicBlocks } from "../../api/appModels/product";
// Hooks
import useApi from "../../Hooks/useApi";
// UI-Components
import Container from "../../uicomponents/General/Container";
import BannerSlider from "../../uicomponents/Slider/BannerSlider";
import Screen from "../../uicomponents/General/Screen";
import TopProductPattern from "../../uicomponents/Category/pattern/TopProductPattern";
import SliderPlaceholder from "../../uicomponents/Skeleton/Sliders/SliderPlaceholder";
import CategoryAvatarPlaceholder from "../../uicomponents/Skeleton/Category/CategoryAvatarPlaceholder";
import CategoryBlocks from "../../uicomponents/Product/blocks/CategoryBlocks";
import Header from "../../uicomponents/header/Header";
import ActivityIndicator from "../../uicomponents/Loaders/ActivityIndicator";
const HomeScreen = () => {
const {
data: topCategoryList,
loading: topCategoryLoading,
request: categoryRequest,
} = useApi(topCategory);
const {
data: bannersList,
loading: bannerLoading,
request: bannersRequest,
} = useApi(banners);
const {
loading: blocksLoading,
request: blocksRequest,
} = useApi(dinamicBlocks);
const [blocks, setBlocks] = useState([]);
const [indexing, setIndexing] = useState(1);
const [countBlocks, setCountBlocks] = useState(0);
const [loader, setLoader] = useState(false);
useEffect(() => {
// Calling Api`s
categoryRequest();
bannersRequest();
blocksRequest((item) => {
setIndexing(indexing + 1);
setBlocks(item["blocks"]);
setCountBlocks(item["count"]);
}, 1);
}, []);
const loadMore = () => {
if (!blocksLoading) {
blocksRequest((item) => {
setIndexing(indexing + 1);
setBlocks(blocks.concat(item["blocks"]));
console.log(item);
}, indexing);
setLoader(indexing != countBlocks);
}
};
return (
<Screen>
<FlatList
data={blocks}
keyExtractor={(item) => item.categories.toString()}
ListHeaderComponent={
<>
<Header />
{bannerLoading ? (
<SliderPlaceholder />
) : (
<BannerSlider data={bannersList} dots />
)}
<Container>
<View style={{ paddingTop: 10 }}>
{topCategoryLoading ? (
<CategoryAvatarPlaceholder />
) : (
<TopProductPattern data={topCategoryList} />
)}
</View>
</Container>
</>
}
ListFooterComponent={<ActivityIndicator visible={loader} />}
renderItem={({ item }) => (
<CategoryBlocks title={item.blocks_name} data={item.categoriesList} />
)}
onEndReached={loadMore}
onEndReachedThreshold={0.1}
/>
</Screen>
);
};
export default HomeScreen;
const styles = StyleSheet.create({});
Here is my code which fetches API
import { useState } from "react";
const useApi = (apiFunc) => {
const [data, setData] = useState([]);
const [error, setError] = useState("");
const [loading, setLoading] = useState(true);
const request = async (callBack = () => {}, ...args) => {
setLoading(true);
const response = await apiFunc(...args);
if (!response.ok) return setError(response.problem);
setLoading(false);
setError("");
setData(response.data);
if (response.ok) {
callBack(response.data);
}
};
return {
data,
error,
loading,
request,
setLoading,
setData,
};
};
export default useApi;
I think there is problem with RN-bridge HELP ME PLEASE !
Probably it's happened because you set onEndReachedThreshold={0.1} and when data was loaded one more time request will be send and it's make problem.
So you can increase this value for example to 0.7.
In the official react native website This is how it is explained onEndReachedThreshold:
"How far from the end (in units of visible length of the list) the bottom edge of the list must be from the end of the content to trigger the onEndReached callback. Thus a value of 0.5 will trigger onEndReached when the end of the content is within half the visible length of the list."

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

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();
}, []);
...
}

Categories

Resources