1

here is the function i am using to update my news website project with infinite scrolling:

import React,{ Component } from "react";
import NewsItem from './NewsItem';
import Spinner from "./Spinner";
import PropTypes from "prop-types";
import InfiniteScroll from "react-infinite-scroll-component";

export class News extends Component {
    static defaultProps={
        country: "in",
        pageSize: 6,
        category: 'general',
        totalResults: 0
    }
    static propTypes={
        country: PropTypes.string,
        pageSize: PropTypes.number,
        category: PropTypes.string
    }
    constructor(props) {
        super(props);
        this.state={
            articles: [],
            loading: true,
            page: 1
        }
    }
    async updateNews() {
        const url=`https://newsapi.org/v2/top-headlines?country=${this.props.country}&category=${this.props.category}&apiKey=[apikey]&page=${this.props.page}&pageSize=${this.props.pageSize}`;
        this.setState({ loading: true })
        let data=await fetch(url);
        let parsedData=await data.json()
        this.setState({
            articles: parsedData.articles,
            totalResults: parsedData.totalResults,
            loading: false
        })
    }
    async componentDidMount() {
        this.updateNews()
    }
    // prevClick=async () => {
    //     this.setState({ page: this.state.page-1 })
    // }
    // nextClick=async () => {
    //     this.setState({ page: this.state.page+1 })
    //     this.updateNews();
    // }
    fetchMoreData=async () => {
        this.setState({ page: this.state.page+1 })
        console.log(this.state.page)
        const url=`https://newsapi.org/v2/top-headlines?country=${this.props.country}&category=${this.props.category}&apiKey=[api key]&page=${this.props.page}&pageSize=${this.props.pageSize}`;
        let data=await fetch(url);
        let parsedData=await data.json()
        this.setState({
            articles: this.state.articles.concat(parsedData.articles),
            totalResults: parsedData.totalResults
        })
    }
    render() {
        return (
            <div className="container my-4">
                <h2 className="my-5 text-center">Top Headlines of today!</h2>
                <InfiniteScroll
                    dataLength={this.state.articles.length}
                    next={this.fetchMoreData}
                    hasMore={this.state.articles.length!==this.state.totalResults}
                    loader={<Spinner />}>
                    <div>
                        <div className="row">
                            {this.state.articles.map((element) => {
                                return <div className="col-md-4" key={element.title}>
                                    <NewsItem title={element.title} description={element.description} imageUrl={element.urlToImage} newsUrl={element.url} author={element.author} date={element.publishedAt} />
                                </div>
                            })}
                        </div>
                    </div>
                </InfiniteScroll>
            </div>
        )
    }
}

export default News

the issue is that change in state of page shows up in the console but when the page is updated, the same data/news gets repeated. I tried using 'this.state.page' instead of 'this.props.page' but there is no difference i guess. Is there something i am missing?

3 Answers3

1

Yes the problem is with your fetchMoreData function. The way state update work is that it does not update instantly instated its batch all the state update and update it into the next cycle.

You can run this code to better understand this example.

import react, { useState, useEffect } from "react";
 
const App = () => {
      const [state, setstate] = useState(1);
    
      const handleState = () => {
        console.log("before", state);
        setstate(state + 1);
        console.log("after", state);
      };
      return (
        <div>
          <button onClick={handleState}>Check</button>
        </div>
      );
    };
    
    export default App;    

The solution of your problem is that take out your page size from state add 1 then after success call increment it

fetchMoreData = async () => {
    let page= this.state.page + 1; // increment like this
    const url = `https://newsapi.org/v2/top-headlines?country=${this.props.country}&category=${this.props.category}&apiKey=[api key]&page=${page}&pageSize=${this.props.pageSize}`;
    let data = await fetch(url);
    let parsedData = await data.json();

   this.setState({
      articles: this.state.articles.concat(parsedData.articles),
      totalResults: parsedData.totalResults,
      page: page
    });
  };
Alpha
  • 1,413
  • 3
  • 9
  • 23
  • page and pageSize are different pageSize is a prop that i forwarded from app.js to decide how many items get loaded at once and page is the variable which stores the page no. (so the name pageNo would be more suitable here i think) but i get what you are saying, i did try that but for some reason even that didn't work, is it possible that the updateNews() function is interfering with componentDidMount() thus altering the output? – kashish patel Aug 18 '22 at 10:24
  • Yes I have mistakenly give incorrect variable name I meant to say page. – Alpha Aug 18 '22 at 10:42
  • oh ok, that actually worked but there is a minor issue, the first page gets repeated once but then everything is in order. is there a bug in there somewhere? – kashish patel Aug 18 '22 at 10:46
  • Yes I know this issue you can get the idea from this question https://stackoverflow.com/questions/32412366/infinite-scroll-loading-content-twice. – Alpha Aug 18 '22 at 10:56
  • Mainly you to check weather the user scroll the page or not because – Alpha Aug 18 '22 at 11:06
  • I saw the comment and understood what was going on but replacing the increment line doesn't work for me, there is a problem with initialization as if i try to initialize it from the state and increment after the code runs then the old problem just repeats again. Not sure how do i do what that question is showing, any help? – kashish patel Aug 18 '22 at 11:11
  • No that was not the issue – Alpha Aug 18 '22 at 11:24
  • you have to play around with scrollThreshold props react-infinite-scroll-component. Basically what happing it calling the fetchMoreData in the initial call that why we have to check weather the page get scroll or not then call fetchMoreData. See the docs https://www.npmjs.com/package/react-infinite-scroll-component – Alpha Aug 18 '22 at 11:28
  • to check if there is more data, the function is already checking that (line 65). I don't understand what you are saying, can you explain it properly? – kashish patel Aug 18 '22 at 12:09
0

Here is the problem.

You shouldn't use the same object or array when updating the state. If so, the component doesn't know state as updated.

So for line 8: You should copy your array with spread operator ... and concat that array.

Phillip
  • 170
  • 4
0

the problem here was that the increment in the page wasn't happening with set State, so the trick was to replace 'this.props.page' from the api url with a different variable to store the page no. and then incrementing it (see the code below to for this to make sense) so that the page gets incremented later but we do get the correct data. I discovered this delay it later on when i was turning the class based components to function based(with hooks).(the delay might still be there so just make sure to cover the delay, by adding 1 in the url too, i.e., 'pageNo+1' instead of 'pageNo' although this won't be necessary if you are using function based components with hooks)

The change is quite a lot but if i change it to function based, then the page counter is initialized outside the function and then just increment it inside as the mathematical incrementing is faster then using the setState or anything else. here is the proper working code:

import React,{ useEffect,useState } from "react";
import NewsItem from './NewsItem';
import Spinner from './Spinner'
import PropTypes from "prop-types";
import InfiniteScroll from "react-infinite-scroll-component";

const News=(props) => {
    const [articles,setarticles]=useState([])
    const [loading,setloading]=useState(true)
    const [page,setpage]=useState(1)
    const [totalResults,settotalResults]=useState(0)
    const updateNews=async () => {
        const url=`https://newsapi.org/v2/top-headlines?country=${props.country}&category=${props.category}&apiKey=${props.apiKey}&page=${pageNo}&pageSize=${props.pageSize}`;
        let data=await fetch(url);
        let parsedData=await data.json()
        setarticles(parsedData.articles)
        settotalResults(parsedData.totalResults)
        setloading(false)

    }
    useEffect(() => {
        updateNews()
    },[])
    let pageNo=0;
    const fetchMoreData=async () => {
        pageNo=page+1
        const url=`https://newsapi.org/v2/top-headlines?country=${props.country}&category=${props.category}&apiKey=${props.apiKey}&page=${pageNo}&pageSize=${props.pageSize}`;
        let data=await fetch(url);
        let parsedData=await data.json()
        setarticles(articles.concat(parsedData.articles))
        settotalResults(parsedData.totalResults)
        setpage(pageNo)
    }
    return (
        <>
            <h1 className="text-center" style={{ margin: '35px 0px',marginTop: '90px' }}>Top Headlines of today!</h1>
            <InfiniteScroll
                dataLength={articles.length}
                next={fetchMoreData}
                hasMore={articles.length!==totalResults}
                loader={<Spinner />}>
                <div className="container">

                    <div className="row">
                        {articles.map((element) => {
                            return <div className="col-md-4" key={element.title}>
                                <NewsItem title={element.title} description={element.description} imageUrl={element.urlToImage} newsUrl={element.url} author={element.author} date={element.publishedAt} />
                            </div>
                        })}
                    </div>
                </div>
            </InfiniteScroll>
        </>
    )
}
News.defaultProps={
    country: "in",
    pageSize: 6,
    category: 'general',
    totalResults: 0
}
News.propTypes={
    country: PropTypes.string,
    pageSize: PropTypes.number,
    category: PropTypes.string
}
export default News

thank you everyone who helped me in this, as i was able to solve the problem partially but not completely now its done after a good nap.

have a nice day!!