Infinite scroll in React - reactjs

I'm trying to add infinite scroll in my component. I somehow did it but it is not working as expected. Whenever, I scroll to bottom it adds more data to APIData but it just duplicates the data which has been already fetched instead of adding new data. And, even when I'm at bottom, the scroll function still runs and fetching the data again and again which has already been fetched. And I'm also getting a warning that I didn't include getData in the dependency array. What's happening and why?
export default function News({ category }) {
const [APIData, setAPIData] = useState([])
const [page, setPage] = useState(1)
const pageSize = 20
async function getData() {
let response = await fetch(`https://newsapi.org/v2/top-headlines?country=in&category=${category}&pageSize=${pageSize}&page=${page}&apiKey={APIKey}`)
let parsedResponse = await response.json()
setAPIData(APIData.concat(parsedResponse.articles))
}
useEffect(() => {
getData()
}, [category])
window.addEventListener('scroll', function () {
if (window.innerHeight + document.documentElement.scrollTop === document.scrollingElement.scrollHeight && page === 1) {
setPage(2)
getData()
console.log('working')
}
}
)

It can be most likely due to this https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous
You can verify that what the page no while in the getData function by console
async function getData(category,page) {
let response = await fetch(`https://newsapi.org/v2/top-headlines?country=in&category=${category}&pageSize=${pageSize}&page=${page}&apiKey={APIKey}`)
let parsedResponse = await response.json()
setAPIData(APIData.concat(parsedResponse.articles))
}
useEffect(() => {
getData(category,page)
}, [category,page])
window.addEventListener('scroll', function () {
if (window.innerHeight + document.documentElement.scrollTop === document.scrollingElement.scrollHeight && page === 1) {
setPage(2)
console.log('working')
}
}

Add new items to state as follow:
setAPIData(previousState => [...previousState, ...parsedResponse.articles])

Related

useEffect doesn't run after rendering

I'm kind of confused about how useEffect is triggered and how it work. I wrote a function like this but the useEffect doesn't run at all. I want to fetch the data from the API and then render a page based on the data. But it doesn't trigger the useEffect. If I don't use the useEffect, it will render the page three times.
async function getData() {
var tmpArrData = [];
await fetch("this API is hidden due to the privacy of the company - sorry")
.then((res) => res.json())
.then((data) => {
console.log("data", data);
tmpArrData = data;
});
console.log("tmpData ", tmpArrData);
return tmpArrData;
}
function App() {
const [arrData, setArrData] = useState();
const [loadData, setLoadData] = useState(false);
useEffect(() => {
console.log("if it works, this line should be shown");
const tmpArrData = getData();
setArrData(tmpArrData);
}, [arrData]);
const data = arrData[0];
console.log(data);
return (
<GifCompoment
id = {data.id}
name = {data.name}
activeTimeTo = {data.activeTimeTo}
activeTimeFrom = {data.activeTimeFrom}
requiredPoints = {data.requiredPoints}
imageUrl = {data.imageUrl}
/>
);
}
export default App;
The useEffect hook is guaranteed to run at least once at the end of the initial render.
getData is an async function and the useEffect callback code is not waiting for it to resolve. Easy solution is to chain from the implicitly returned Promise from getData and access the resolved value to update the arrData state. Make sure to remove the state from the useEffect's dependency array so that you don't create a render loop.
The getData implementation could be clean/tightened up by just returning the fetch result, no need to save into a temp variable first.
async function getData() {
return await fetch(".....")
.then((res) => res.json());
}
useEffect(() => {
console.log("if it works, this line should be shown");
getData().then((data) => {
setArrData(data);
});
}, []); // <-- empty dependency so effect called once on mount
Additionally, since arrData is initially undefined, arrData[0] is likely to throw an error. You may want to provide valid initial state, and a fallback value in case the first element is undefined, so you don't attempt to access properties of an undefined object.
const [arrData, setArrData] = useState([]);
...
const data = arrData[0] || {}; // data is at least an object
return (
<GifCompoment
id={data.id}
name={data.name}
activeTimeTo={data.activeTimeTo}
activeTimeFrom={data.activeTimeFrom}
requiredPoints={data.requiredPoints}
imageUrl={data.imageUrl}
/>
);
You should call state setter insede of Promise
function App() {
const [arrData, setArrData] = useState();
function getData() {
fetch("/api/hidden")
.then((res) => res.json())
.then((data) => setArrData(data));
}
useEffect(() => {
console.log("if it works, this line should be shown");
getData();
}, []);
return ...
}
By combining the answer from Drew Reese and Artyom Vancyan, I have solved my problem. I think the key points are setState right in the then function .then((data) => setArrData(data)) ,don't put the dependency in the useEffect, and await inside the useEffect. Thank you guy super ultra very much. Big love
useEffect(() => {
console.log("if it works, this line should be shown");
const getData = async () => {
await fetch("hidden API")
.then((ref) => ref.json())
.then((data) => {
setArrData(data);
});
}
getData();
}, []);
function App() {
const [arrData, setArrData] = useState([]);
const [loadData, setLoadData] = useState(false);
const async getData=()=> {
var tmpArrData = [];
await fetch("this API is hidden due to the privacy of the company - sorry")
.then((res) => res.json())
.then((data) => {
console.log("data", data);
setArrData(tmpArrData);
});
console.log("tmpData ", tmpArrData);
return tmpArrData;
}
useEffect(() => {
console.log("if it works, this line should be shown");
const callApi =async()=>{
await getData();
}
}, [arrData]);
const data = arrData[0];
console.log(data);
return (
<GifCompoment
id = {data.id}
name = {data.name}
activeTimeTo = {data.activeTimeTo}
activeTimeFrom = {data.activeTimeFrom}
requiredPoints = {data.requiredPoints}
imageUrl = {data.imageUrl}
/>
);
}
export default App;
Page will be rendered three to four times it's normal.

Get an empty array when use array of objects in filter function React

I am new to react and try to get data from the database and view data in frontend. This is the code I tried.
function ViewPost() {
const { postId } = useParams();
console.log(postId);
const [post, setPost] = useState({});
useEffect(()=>{
getOnePost();
}, []);
useEffect(()=>{
if (post && post.location) {
console.log(post.location);
console.log(post.location.longitude);
console.log(post.location.latitude);
}
}, [post]);
const getOnePost = async () => {
try {
const response = await axios.get(`/buyerGetOnePost/${postId}`)
console.log(response);
const allPost=response.data.onePost;
setPost(allPost);
} catch (error) {
console.error(`Error: ${error}`)
}
}
console.log(post);
console.log(post.wasteItemList);
const [offers, setOffers] = useState([]);
useEffect(()=>{
getAllOffers();
}, []);
const getAllOffers = async () => {
await axios.get(`/viewPendingSellerOffers`)
.then ((response)=>{
const allNotes=response.data.existingOffers;
setOffers(allNotes);
})
.catch(error=>console.error(`Error: ${error}`));
}
console.log(offers);
const wasteItem = offers?.filter(wasteItems => wasteItems.status==='accepted' && wasteItems.wasteItemsListId===post?.wasteItemList?._id);
console.log(wasteItem);
}
When I call the first API I get these results. This is an image of results.
In the above image, there is a length 2 array of objects called as wasteItemList. Then I call the second API and get these results.
This image shows length 8 array of objects. Then I try to filter the data of these two arrays using this const wasteItem = offers?.filter(wasteItems => wasteItems.status === 'accepted' && wasteItems.wasteItemsListId === post?.wasteItemList?._id); code. But I get a length 0 empty array as the results of this filter function. But when I try an ID of a wasteItemList array
6112679258125b0418844368 instead of using this post?.wasteItemList?._id code I get the correct result. What is the problem here? How do I solve this problem?
Edited code:
function ViewPost() {
const { postId } = useParams();
const [post, setPost] = useState(undefined);
const [offers, setOffers] = useState(undefined);
useEffect(() => {
setPost(undefined);
axios
.get(`/buyerGetOnePost/${postId}`)
.then((resp) => setPost(resp.data.onePost))
.catch((err) => console.error(err));
}, [postId]);
useEffect(() => {
axios
.get(`/viewPendingSellerOffers`)
.then((response) => setOffers(response.data.existingOffers))
.catch((err) => console.error(err));
}, []);
useEffect(()=>{
if (post && post.location) {
console.log(post.location);
console.log(post.location.longitude);
console.log(post.location.latitude);
}
}, [post]);
console.log(post);
console.log(post?.wasteItemList);
console.log(offers);
const wasteItem = offers?.filter(wasteItems => wasteItems.status==='accepted' && wasteItems.wasteItemsListId===post?.wasteItemList?._id);
console.log(wasteItem);
}
useEffect runs asynchronously so your post will not be available
on your getAllOffers function which is located in your second
useEffect.
You will need to make your getOnePost() and getAllOffers() to
run synchronously within a single useEffect.
Or the problem is in your condition checks as I can't tell much only
by your given array picture.

ts/react - fetch in useEffect gets called multiple times

in my functional component I want to fetch data once the component mounts. But unfortunately, the request gets fired three times, until it stops. Can you tell me why?
const [rows, setRows] = useState<any[]>([]);
const [tableReady, setTableReady] = useState<boolean>(false);
const [data, setData] = useState<any[]>([]);
const getData = async () => {
const user = await Amplify.Auth.currentAuthenticatedUser();
const token = user.signInUserSession.idToken.jwtToken;
const apiurl = 'xxx';
fetch(apiurl, {
method: 'GET',
headers: {
'Authorization': token
}
})
.then(res => res.json())
.then((result) => {
setData(result);
})
.catch(console.log)
}
useEffect(() => {
if (!tableReady) {
getData();
if (data.length > 0) {
data.forEach((element, i) => {
const convertedId: number = +element.id;
setRows(rows => [...rows, (createData(convertedId, element.user))]);
});
setTableReady(true);
}
}
}, []);
return (
<div className={classes.root}>
<MUIDataTable
title={""}
data={rows}
columns={columns}
/>
</div>
);
I updated my question due to the comment.
The useEffect is missing a dependency array, so its callback is invoked every time the component renders.
Solution
Add a dependency array.
useEffect(() => {
if (!tableReady) {
getData();
if (data.length > 0) {
data.forEach((element, i) => {
const convertedId: number = +element.id;
rows.push(convertedId);
});
setTableReady(true);
}
}
}, []); // <-- dependency array
An empty dependency array will run the effect once when the component mounts. If you want it to ran when any specific value(s) update then add these to the dependency array.
See Conditionally firing an effect
Edit
It doesn't appear there is any need to store a data state since it's used to populate the rows state. Since React state updates are asynchronously processed, and useEffect callbacks are 100% synchronous, when you call getData and don't wait for the data to populate, then the rest of the effect callback is using the initially empty data array.
I suggest returning the fetch request from getData and just process the response data directly into your rows state.
const getData = async () => {
const user = await Amplify.Auth.currentAuthenticatedUser();
const token = user.signInUserSession.idToken.jwtToken;
const apiurl = 'xxx';
return fetch(apiurl, {
method: 'GET',
headers: {
'Authorization': token
}
});
}
useEffect(() => {
if (!tableReady) {
getData()
.then(res => res.json())
.then(data => {
if (data.length) {
setRows(data.map(element => createData(+element.id, element.user)))
}
})
.catch(console.error)
.finally(() => setTableReady(true));
}
}, []);

setState never gets set in useEffect after API call responds with data

I'm trying to update state immediately after data comes in from the API. The data is coming in, I can see it using the console.log right below my API request. All of the data is right but for some reason, setState never sets in my hook. It just returns and empty array even after the console displays data.
const [experienceData, setExperienceData] = useState([]);
const { match = {} } = props;
useEffect(() => {
async function fetchData() {
if (Object.keys(match.params).length > 0) {
const response = await ApiService.getExperiences(match.params.experieneId);
console.log(response)
setExperienceData(response)
}
}
fetchData();
}, []);
I must be doing something wrong but I can't figure out what that is. Hoping someone on here has run into the same issue.
UPDATE: I just changed everything over the a class and duplicated the exact code on another file and ran into the exact same issue. The console updates with the data, but the setState on the line immediately after the data does not setState.
async componentDidMount() {
if (Object.keys(this.props.match.params).length > 0) {
const response = await ApiService.getExperiences(this.props.match.params.experieneId);
console.log(response[0])
this.setState({ experienceData: response[0], occurrenceData: response[0].occurrences });
}
}
You have to useSetState in a proper way, the issue is in the setExperienceData
const [experienceData, setExperienceData] = useState({response:""});
const { match = {} } = props;
useEffect(() => {
async function fetchData() {
if (Object.keys(props.match.params).length > 0) {
const response = await ApiService.getExperiences(match.params.experieneId);
console.log(response)
setExperienceData(experienceData => ({ ...experienceData, response: response }));
}
}
fetchData();
}, []);
return(<div>check {experienceData.response}</div>)
I see you left the dependency array empty. This tells React to run this effect only once: when the component first renders. If you want your useEffect to respect your state hook, put setExperienceData inside the dependency array
const [experienceData, setExperienceData] = useState([]);
const { match = {} } = props;
useEffect(() => {
fetchData();
}, [props.match.params]);
const async fetchData = () => {
if (Object.keys(match.params).length > 0) {
const response = await ApiService.getExperiences(match.params.experieneId);
console.log(response)
setExperienceData([...response])
}
}
Could you please try passing [match.params] as the second argument to your useEffect.

Promise not working in React useEffect in combination with Firebase

I want to get data from firebase inside a useEffect function like this:
useEffect(() => {
/** nope */
async function fetchData() {
let dataObject = {};
let dataArray = [];
setAttendees({});
// You can await here
if (newData[listRedux]) {
const request = await Object.keys(newData[listRedux] .
[1].attendees).map(
user => {
usersRef.child(user).on('value', snap => {
dataObject[snap.key] = snap.val();
setAttendees(dataObject);
console.log(dataObject);
let comp = (
<Avatar
key={snap.key}
size="small"
source={snap.val().avatar}
alt={snap.val().name}
/>
);
dataArray.push(comp);
setAttendeesComp(dataArray);
});
}
);
// Wait for all requests, and then setState
await Promise.all(request).then(() => {
console.log('done');
});
}
}
fetchData();
}, [newData, listRedux]);
Now the second console.log inside the promise all will first show then the first console.log, meaning the request was not done yet.
How can i improve my code so the request and the states are first being set and then continue with the rest?
export default function Example() {
const [data, dataSet] = useState(false)
const [attendees, setAttendees] = useState(false)
async function fetchMyAPI() {
let response = await fetch('api/data')
response = await res.json()
console.log(response);
dataSet(response)
}
useEffect(() => {
if (!attendees) return
fetchMyAPI();
}, [attendees, newData, listRedux]);
useEffect(() => {
setAttendees({})
}, [])
More examples here:

Resources