I was expecting just 10 pictures on initial load, but ended up getting 20 because of the functional update of the state.
Result:
if in use the normal approach the app will show the expected 10,i want to add if statement to the state so i need to use the functional update
app code below
import React, { useState, useEffect } from "react";
import { FaSearch } from "react-icons/fa";
import Photo from "./Photo";
const clientID = `?
client_id=${import.meta.env.VITE_ACCESS_KEY}`;
const mainUrl = `https://api.unsplash.com/photos/`;
const searchUrl = `https://api.unsplash.com/search/photos/`;
function App() {
const [loading, setLoading] = useState(false);
const [photos, setPhotos] = useState([]);
const [page, setPage] = useState(1);
const [query, setQuery] = useState("");
const fetchImages = async () => {
setLoading(true);
let url;
// const urlPage = `&page=${page}`;
const urlPage = `&page=${page}`;
const urlQuery = `&query=${query}`;
url = `${mainUrl}${clientID}${urlPage}`;
try {
const response = await fetch(url);
const data = await response.json();
console.log(data);
setPhotos((oldPhoto) => {
return [...oldPhoto, ...data];
});
setLoading(false);
} catch (error) {
console.log(error);
setLoading(false);
}
};
useEffect(() => {
fetchImages();
}, [page]);
useEffect(() => {
const event = window.addEventListener("scroll", () => {
if (
!loading &&
window.innerHeight + window.scrollY >= document.body.scrollHeight - 2
) {
setPage((oldPage) => {
return oldPage + 1;
});
}
});
return () => window.removeEventListener("scroll", event);
}, []);
const handleSubmit = (e) => {
e.preventDefault();
};
return (
<main>
<section className="search">
<form className="search-form">
<input
type="text"
placeholder="search"
className="form-input"
value={query}
onChange={(e) => setQuery(e.target.value)}
/>
<button type="submit" className="submit-btn" onClick={handleSubmit}>
<FaSearch />
</button>
</form>
</section>
<section className="photos">
<div className="photos-center">
{photos.map((image) => {
return <Photo key={image.id} {...image} />;
})}
</div>
{loading && <h2 className="loading">loading...</h2>}
</section>
</main>
);
}
export default App;
2
Answers
What was causing the duplicated image was the react strict mode, when in development React executes all useEffects twice, to help catch some bugs. You could disable it (by removing the strictmode tags) to prevent this, but it’s not recommended.
So to stop this i added logic to the fetch, the param isFetchingMore, in the useEffect it’s used with the default value (false)
Other thing that was necessary to make it work was to create refs for the loading and page, when you add a manual event listener, it doesn’t get the updated state, it get’s only the initial value. For that it’s necessary to use refs.
Here are the changes:
https://codesandbox.io/s/cocky-mopsa-5ho92v?file=/src/App.js
I saw your code. In line 22, you add new photos to the old ones, that’s why if it runs twice, your state will contains twice of each photo. I changed it to normal
I understand that you did it, because you want to
lazyLoad
the photos, by eachfetch
, new photos will be added. I would do it differently.First of all, you can easily use a native
html
attribute for images to lazy load:<img src="image.jpg" alt="..." loading="lazy" />
check: https://developer.mozilla.org/en-US/docs/Web/Performance/Lazy_loading#images_and_iframes`
This way you won’t have to deal with the problem inside your
React
logic. I saw that you have an<img/>
tag inside yourPhotos
component.With all respect to the other answers, I wouldn’t take my development environment out of
strictMode
only to solve an issue. In my opinion an app should work inside and outsidestrictMode
, and that’s why I appreciate you asking this question here.