I am trying to call a component that shows the details of a notification when the notification is clicked. However, I kept on getting an error of too many re-renders.
This is my Notifications code
This component calls the database to get the list of notifications and then sets the first notification as the default notification clicked.
const Notification = (hospital) => {
const [users, setUsers] = useState([]);
const [search, setSearch] = useState(null);
const [status, setStatus] = useState(null);
const [notifDetails, setNotification] = useState();
useEffect(async () => {
await axios
.get("/notifications")
.then((res) => {
const result = res.data;
setUsers(result);
setNotification(result[0]);
})
.catch((err) => {
console.error(err);
});
}, []);
return(
<div className="hospital-notif-container">
{filteredList(users, status, search).map((details, index) => {
for (var i = 0; i < details.receiver.length; i++) {
if (
(details.receiver[i].id === hospital.PK ||
details.receiver[i].id === "others") &&
details.sender.id !== hospital.PK
) {
return (
<div
className="hospital-notif-row"
key={index}
onClick={() => setNotification(details)}
>
<div className="hospital-notif-row">
{details.name}
</div>
</div>
);
}
}
return null;
})}
</div>
<NotificationDetails details={notifDetails} />
);
}
For NotificationDetails:
This function is triggered when a notification is clicked from Notifications. The error is said to be coming from this component.
const NotificationDetails = ({ details }) => {
const [loading, setLoading] = useState(true);
useEffect(() => {
if (Object.keys(details).length != 0) {
setLoading(false);
}
}, [details]);
if (!loading) {
return (
<>
<div className="hospital-details-container">
<h2>{details.sender.name}</h2>
</div>
</>
);
} else {return (<div>Loading</div>);}
};
What should I do to limit the re-render? Should I change the second argument of the useEffects call? Or am I missing something in my component?
I tried calling console.log from NotificationDetails and it shows that it is infinitely rendering with the data I set in axios which is result[0]. How is this happening?
Your problem should be in NotificationDetails rendering. You should write something like:
const NotificationDetails = ({ details }) => {
const [loading, setLoading] = useState(true);
useEffect(() => {
if (details.length != 0) {
setLoading(false);
}
}, [details]);
return (
<div>
{loading &&
<div className="hospital-details-container">
<div className="hospital-details-header">
<h2>{details.sender.name}</h2>
</div>
</div>
}
{!loading &&
<div>
<ReactBootStrap.Spinner animation="border" />
</div>
}
</div>
);
}
With return outside the condition statement.
EDIT
Now I noted that you have an async useEffect that is an antipattern. You should modify your useEffect in this way:
useEffect(() => {
(async () => {
await axios
.get("/notifications")
.then((res) => {
const result = res.data;
setUsers(result);
setNotification(result[0]);
})
.catch((err) => {
console.error(err);
});
})()
}, []);
Related
I´m new to react. I´m trying to fetch an endpoints array. and I want to update the api's status every 15 seconds. I´m trying to do this
export const endpoints: string[] = [
"accounts/health/status",
"assets/health/status",
"customers/health/status",
"datapoints/health/status",
"devices/health/status",
"documents/health/status",
"forms/health/status",
"invites/health/status",
"media/health/status",
"messages/health/status",
"namespaces/health/status",
"orders/health/status",
"patients/health/status",
"relationships/health/status",
"rules/health/status",
"templates/health/status",
"users/health/status",
"workflows/health/status",
];
and I have this proxy in my package.json
"proxy": "https://api.factoryfour.com/",
Here the rest of my code
const [data, setData] = useState<Response[]>([]);
const [loading, setLoading] = useState<boolean>(false);
const [error, setError] = useState<string[] | null[]>([]);
const effectRan = useRef(false);
const fetching = async () => {
setLoading(true);
endpoints.map(async (endpoint) => {
return await axios
.get(endpoint)
.then((res) => {
setData((prev) => [...prev, res.data]);
})
.catch((err) => {
setError([...error, err.message]);
});
});
setLoading(false);
};
useEffect(() => {
if (!effectRan.current) {
fetching();
}
return () => {
effectRan.current = true;
};
});
useEffect(() => {
setTimeout(async () => {
setData([]);
setLoading(true);
setError([]);
await fetching();
}, 15000);
}, []);
but when the seTimeout runs every card duplicates and the state gets more data than before. even though I´m reseting the state to setData([]) I just want to update the api's status. What can i do?
if (loading) return <Spinner />;
return (
<div className="card-container">
{data.length ? (
data.map((item) => {
return (
<Card
key={generateKey()}
hostname={item.hostname}
message={item.message}
success={item.success}
time={item.time}
/>
);
})
) : (
<Spinner />
)}
{error.length
? error.map((err) => (
<ErrorCard key={generateKey()} message={err as string} />
))
: null}
</div>
```
Theres a few things wrong here and one or more probably fixes it:
You keep a ref around to track the first fetch but theres no need as you can do that by virtue of using [] in an effects deps array, which you already have.
The loading state does not wait until all requests in flight finished.
The 15 second interval does not wait until all requests launched are finished.
You dont clear down the timer if the component unmounts and remounts.
The data is not keyed against the endpoint which could land you in trouble if using React strictmode that runs affects twice in dev mode.
Your code, by design it seems, does append data each time one of the requests comes back -- but I think that was intentional?
const [data, setData] = useState<Record<string, Response>>({});
const [loading, setLoading] = useState<boolean>(false);
const [error, setError] = useState<Record<string, string | null>>({});
const fetching = async () => {
setLoading(true);
await Promise.all(
endpoints.map((endpoint) => {
return axios
.get(endpoint)
.then((res) => {
setData((prev) => ({...prev, [endpoint]: res.data}));
})
.catch((err) => {
setError((prev) => ({...prev, [endpoint]: err.message}));
});
})
);
setLoading(false);
};
useEffect(() => {
let timer: number | null = null;
const intervalFetch = async () => {
await fetching();
timer = setTimeout(async () => {
setError({});
setData({});
intervalFetch();
}, 15000);
};
intervalFetch();
return () => timer !== null && clearTimeout(timer);
}, []);
if (loading) return <Spinner />;
return (
<div className="card-container">
{Object.values(data).length ? (
Object.values(data).map((item) => {
return (
<Card
key={generateKey()}
hostname={item.hostname}
message={item.message}
success={item.success}
time={item.time}
/>
);
})
) : (
<Spinner />
)}
{Object.values(error).length
? Object.values(error).map((err) => (
<ErrorCard key={generateKey()} message={err as string} />
))
: null}
</div>)
I think this piece of code might be adding additional data instead of overwriting the existing one. Is that what you're trying to do?
setData((prev) => [...prev, res.data]);
I'm trying to fetch some data from the API, but doesn't matter which dependencies I use, useEffect still keeps making an infinite loop, is there something wrong in the code or why it keeps doing that?
function HomePage() {
const [Carousels, setCarousels] = useState([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
const getCarousels = async () => {
setLoading(true);
const genres = ["BestRated", "Newest"];
for (const genre of genres) {
try {
const res = await axios.get(`http://localhost:4000/api/carousels/` + genre);
console.log(res.data);
setCarousels([...Carousels, res.data]);
} catch (err) {
console.log(err);
}
}
setLoading(false);
}
getCarousels();
}, [Carousels]);
return (
<div className='Home'>
<NavBar />
<HeroCard />
{!loading && Carousels.map((carousel) => (
<Carousel key={carousel._id} carousel={carousel} />
))}
<Footer />
</div>
);
}
Use effect called when Carousels changed and Carousels changed inside useEffect.
Use set state with callback
function HomePage() {
const [Carousels, setCarousels] = useState([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
const getCarousels = async () => {
setLoading(true);
const genres = ["BestRated", "Newest"];
for (const genre of genres) {
try {
const res = await axios.get(`http://localhost:4000/api/carousels/` + genre);
console.log(res.data);
setCarousels(carouselsPrev => [...carouselsPrev, res.data]);
} catch (err) {
console.log(err);
}
}
setLoading(false);
}
getCarousels();
}, []);
return (
<div className='Home'>
<NavBar />
<HeroCard />
{!loading && Carousels.map((carousel) => (
<Carousel key={carousel._id} carousel={carousel} />
))}
<Footer />
</div>
);
}
useEffect in your code updates Carousels each run. It sees the updated value and runs again. You could fix it various ways, the easiest would be to add [fetched, setFetched] = useState(false); and use it in your useEffect to check before fetching from the API
I am using the following code for fetching and passing data from firestore.
there are four data in my firestore collection. I am able to fetch all four of them, (i checked this on console), but in NewRecipe component only the first data is getting passed as props. not sure why the other three data not getting passed as props.
this is component where i am fetching data :
const Home = () => {
const [data, setData] = useState(null);
const [isPending, setIsPending] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
setIsPending(true);
projectFirestore
.collection("recipes")
.get()
.then((snapshot) => {
if (snapshot.empty) {
setError("No recipes");
setIsPending(false);
} else {
let result = [];
snapshot.docs.forEach((doc) => {
result.push({ id: doc.id, ...doc.data() });
setData(result);
setIsPending(false);
});
}
})
.catch((e) => {
setError(e);
});
}, []);
console.log("data", data);
return (
<div>
{error && <div className="errors">{error}</div>}
{isPending && <div className="Loading">Loading.......</div>}
{data && <NewRecipe data={data} />}
</div>
);
};
export default Home;
this is NewRecipe component where I am receiving props
const NewRecipe = ({data}) => {
console.log("data new recipe", data)
return (
<div className='recipe_list'>
{data.map((item)=>{
return (
<div key={item.id} className='card'>
<h3>{item.title}</h3>
<p>it takes around {item.cookingTime}</p>
<p>{item.method.substring(0,100)}</p>
<Link to={`/recipes/ ${item.id}`}className='btn'> Read more</Link>
</div>
)})}
</div>
)
}
Try to place setPending(false) outside (just after) the forEach block.
I am trying to setup infinite scrolling using React Hooks, I am getting the data correctly from the node backend (3 dataset per request), and when I scroll new data is also added correctly in the array (in the loadedPlaces state), but the page is going back to top on re render, I need to prevent this behavior. How do I prevent this, and below is my code
import React, { useEffect, useState } from "react";
import "./App.css";
function App() {
const [page, setPage] = useState(1);
const [loadedPlaces, setLoadedPlaces] = useState([]);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
const getPlaces = async () => {
try {
setIsLoading(true);
const url = `http://localhost:5000/api/places/user/${id}?page=${page}&size=3`;
const responseData = await fetch(url);
const data = await responseData.json();
console.log(data);
setLoadedPlaces((prev) => [...prev, ...data.places]);
setIsLoading(false);
} catch (error) {}
};
getPlaces();
}, [page]);
window.onscroll = function (e) {
if (
window.innerHeight + document.documentElement.scrollTop ===
document.documentElement.offsetHeight
) {
setPage(page + 1);
}
};
return (
<div className='App'>
<h1>Infinite Scroll</h1>
{!isLoading &&
loadedPlaces.length > 0 &&
loadedPlaces.map((place, index) => (
<div key={index} className={"container"}>
<h1>{place.location.lat}</h1>
<h1>{place.location.lng}</h1>
<h1>{place.title}</h1>
<h1>{place.description}</h1>
<h1>{place.address}</h1>
<hr></hr>
</div>
))}
</div>
);
}
export default App;
Any help is highly appreciated
This is happening because whenever you scroll you are calling
window.onscroll = function (e) {
if (
window.innerHeight + document.documentElement.scrollTop ===
document.documentElement.offsetHeight
) {
setPage(page + 1);
}
};
And it's changing the page count and that changed page count leads to again run the
useEffect(() => {
const getPlaces = async () => {
try {
setIsLoading(true);
const url = `http://localhost:5000/api/places/user/${id}?page=${page}&size=3`;
const responseData = await fetch(url);
const data = await responseData.json();
console.log(data);
setLoadedPlaces((prev) => [...prev, ...data.places]);
setIsLoading(false);
} catch (error) {}
};
getPlaces();
}, [page]);
and in that function, you are doing setIsLoading(true) so that it is again rendering this because of
{!isLoading && <-----
loadedPlaces.length > 0 &&
loadedPlaces.map((place, index) => (
<div key={index} className={"container"}>
<h1>{place.location.lat}</h1>
<h1>{place.location.lng}</h1>
<h1>{place.title}</h1>
<h1>{place.description}</h1>
<h1>{place.address}</h1>
<hr></hr>
</div>
))}
And that leads you to the top of the page
You can try this approach:
import React, { useEffect, useState } from "react";
import "./App.css";
function App() {
const [page, setPage] = useState(1);
const [loadedPlaces, setLoadedPlaces] = useState([]);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
const getPlaces = async () => {
try {
setIsLoading(true);
const url = `http://localhost:5000/api/places/user/${id}?page=${page}&size=3`;
const responseData = await fetch(url);
const data = await responseData.json();
console.log(data);
setLoadedPlaces((prev) => [...prev, ...data.places]);
setIsLoading(false);
} catch (error) {}
};
getPlaces();
}, [page]);
window.onscroll = function (e) {
if (
window.innerHeight + document.documentElement.scrollTop ===
document.documentElement.offsetHeight
) {
setPage(page + 1);
}
};
return (
<div className='App'>
<h1>Infinite Scroll</h1>
{
loadedPlaces.length > 0 &&
loadedPlaces.map((place, index) => (
<div key={index} className={"container"}>
<h1>{place.location.lat}</h1>
<h1>{place.location.lng}</h1>
<h1>{place.title}</h1>
<h1>{place.description}</h1>
<h1>{place.address}</h1>
<hr></hr>
</div>
))}
</div>
);
}
export default App;
You can add this.
function ScrollToBottom(){
const elementRef = useRef();
useEffect(() => elementRef.current.scrollIntoView());
return <div ref={elementRef} />;
};
And then:
return (
<div className='App'>
<h1>Infinite Scroll</h1>
{!isLoading &&
loadedPlaces.length > 0 &&
loadedPlaces.map((place, index) => (
<div key={index} className={"container"}>
<h1>{place.location.lat}</h1>
<h1>{place.location.lng}</h1>
<h1>{place.title}</h1>
<h1>{place.description}</h1>
<h1>{place.address}</h1>
<hr></hr>
</div>
))}
<ScrollToBottom />
</div>
);
I'm working with tmdb api, I've been using react's useEffect and useContext for minor stuff and it works. I decided to use the api and it has been going into this infinite loop anytime I try to run it. I'm using axios with useEffects and useContext. Below is the component calling axios
enter image description here
function MovieList(props) {
useEffect(() => {
const getMovies = async () => { };
try {
const response = await axios.get(movieUrl, { cancelToken: source.token });
addMovies(response.data.results);
// setLoading(false)
} catch (error) {
if (axios.isCancel(error)) {
console.log('Request canceled', error.message);
} else {
// handle error
throw error
}
}
getMovies();
}, []);
return (
<React.Fragment>
<div className="container-fluid pl-5">
<h2 className="white mt-4">{pageTitle}</h2>
<div className="row">
{movies.map((movie) => {
return <Movie key={movie.id} movie={movie} />;
})}
</div>
</div>
</React.Fragment>
);
}
My code goes into an infinite loop and it's driving me crazy.
And my context code is found below:
export const MovieProvider = (props) => {
const [isFav, setIsFav] = useState(false)
const [movies, setMovies] = useState([]);
const [filtered, setFilteredMovies] = useState([])
const addMovies = (res) => {
setMovies([...res]);
}
const addFavMovies = (res) => {
if(res){
setMovies([...res]);
}
}
const addFilteredMovies = (res) => {
setFilteredMovies([...filtered, res]);
}
return (
<MovieContext.Provider value={{
fav: [isFav, setIsFav],
movies, addMovies, addFavMovies,
filtered, addFilteredMovies,
}}>
{props.children}
</MovieContext.Provider>
)
}