Ourfli
Ourfli

Reputation: 13

The state changed but the render method from react does not trigger

I am currently self teaching myself code and at the moment diving into React.js. I am building a simple app which displays beer information pulled through the punkAPI (https://punkapi.com/documentation/v2).

In my app I wanted to add some kind of "infinite" scroll which would pull more data from the API when the user reaches the bottom of the page.

At the moment, the handleScroll method works, and updates the state of the component but it does not trigger the render method and I was wondering why.

I realize that there are a lot of things wrong with my code and I plan to restructure it, add some booleans in this.state to check if there is still more data to load, to handle errors and to not trigger the handleScroll method so intensively.

However, I am still wondering why there is no rendering happening even though the state is updated.

class BeerList extends Component {
    constructor() {
        super()
        this.state = {
            loading: false,
            beers: []
        }
        this.handleScroll = this.handleScroll.bind(this)
    }

    componentDidMount() {
        window.addEventListener('scroll', this.handleScroll, true);

        this.setState({
            loading: true
        })
        fetch("https://api.punkapi.com/v2/beers?per_page=12")
            .then(response => response.json())
            .then(data => {
                this.setState({
                    loading: false,
                    beers: data
                })
            })
    }

    handleScroll() {
        const checkForNewBeers = () => {
            let lastBeerCard = document.querySelector(".beer-list > div:last-child");
            let lastBeerCardOffset = lastBeerCard.offsetTop + lastBeerCard.clientHeight;
            let pageOffset = window.pageYOffset + window.innerHeight;

            if (pageOffset <= lastBeerCardOffset - 10) {
                return;
            }
            this.setState(prevState => {
                const beers = prevState.beers;
                const page = (prevState.beers.length / 12) + 1;
                fetch(`https://api.punkapi.com/v2/beers?per_page=12&page=${page}`)
                    .then(response => response.json())
                    .then(data => {
                        for (let item of data) {
                            beers.push(item);
                        }
                        console.log({
                            beers: beers
                        });
                        return {
                            beers: beers
                        }
                    });
            });
        }
        document.addEventListener("scroll", function (event) {
            checkForNewBeers();
        });
    }


    render() {
        let beerCards = []
        if (this.state.beers.length > 0) {
            beerCards = this.state.beers.map(beer => {
                return <BeerCard
                key = {
                    beer.id
                }
                img = {
                    beer.image_url
                }
                title = {
                    beer.name
                }
                description = {
                    beer.description
                }
                />
            })
        }

        return ( <
            div className = "container" >
            <
            div className = "row beer-list" > {
                beerCards
            } <
            /div> < /
            div >
        )
     }

}

export default BeerList


So BeerCards are correctly appended when the page is loading then when you scroll the console shows you that the state is updated (way too much but still). I would expect the page loading a shit ton of BeerCards but nothing is happening. Why is that?

Upvotes: 1

Views: 76

Answers (3)

Yves Gurcan
Yves Gurcan

Reputation: 1178

You are so close! By simply adding the keyword "async" in front of "prevState" in "this.setState" and "return await" in front of "fetch", the state of your app should be updated and trigger a rerender as expected.

Upvotes: 0

larz
larz

Reputation: 5766

Instead of returning an object from your async fetch, call setState inside the .then()

let beers = this.state.beers;
const page = (this.state.beers.length / 12) + 1;
fetch(`https://api.punkapi.com/v2/beers?per_page=12&page=${page}`)
    .then(response => response.json())
    .then(data => {
        for (let item of data) {
            beers.push(item);
        }

         this.setState({ beers });
    });

Upvotes: 1

Easwar
Easwar

Reputation: 5402

As soon as you invoke fetch which is the last statement inside setState, an undefined would be returned. Try transforming the setState parameter to an async function:

this.setState(async prevState => {
  const beers = prevState.beers;
  const page = prevState.beers.length / 12 + 1;
  let response = await fetch(
    `https://api.punkapi.com/v2/beers?per_page=12&page=${page}`
  );
  response = await response.json();
  for (const item of response) {
    beers.push(item);
  }
  console.log({
    beers: beers
  });
  return {
    beers: beers
  };
});

Upvotes: 1

Related Questions