0

I have a React component that is supposed to load 6 images at a time as the page is scrolled lower. I want the typical infinite scroll effect YouTube and Reddit now use.

Right now the page opens as it should with 6 images loaded. When I scroll down the app loads another 6 but then it stops. There are 25 images total. I count 13 images that are not loading for some reason. The app is supposed to keep loading images 6 at a time when scrolling down until there are no more images to load.

I am using the Infinite Scroll component here. I've tried installing an older version number '4.5.2' and that didn't work either.

A screen shot of the app at the bottom shows buttons at the top which add or remove 6 images.
The first number shows the increments of 6 images. (It's 0 when 6 images are rendered and 1 when 12 are rendered.)
The number below that shows the number of total images (image URLs in the JSON object the app has received from the back end).

    import React, { ListView } from 'react';
    import InfiniteScroll from 'react-infinite-scroll-component';

    export default class Fb_grid extends React.Component {
        constructor(props) {
            super(props)
            this.state = {
            apiResponse: [],
            page: 0
            }
        }
        componentDidMount() {
        this.callAPI();
        }

        callAPI() {
            fetch('/api/getList')
                .then(res => res.json())
                .then(res => this.setState({ apiResponse: res }))
        }

        next = () => {
        this.setState({ page: this.state.page+1 })
        }

        render() {
            const { apiResponse } = this.state;
            const { page } = this.state;

            if(!apiResponse) {
                return <p className="row">Loading</p>
            }
            return(
            <div className="App">
                <div className="row">
                <button className="testButton" onClick={this.previous}>LESS</button>
                <button className="testButton" onClick={this.next}>MORE</button>
                <br/>{page}<br/>{apiResponse.length}<div className="spacer"/>
                </div>
                <div className="row">
                    <InfiniteScroll
                    dataLength={apiResponse.length}
                    next={this.next}
                    hasMore={true}
                    >
                        {Object.values(apiResponse.slice(0, (this.state.page+1)*6)).map((value, index) => 
                        <a target="_blank" rel="noopener noreferrer" href={value.post}>
                            <img src={value.image} className="image"/>
                        </a>)}
                    </InfiniteScroll>
                </div>
            </div>
            )
        }
    }

App Screen Shot

If you would like to run this component on your machine you will probably want your own JSON stored locally in the component. Of course you will also have to adjust the Object.values(apiResponse.slice(0, (this.state.page+1)*6)).map line accordingly.
Here is what the JSON data my component is using looks like. 25 objects with an image URL and a link for the <a> tag the image is rendered in.

[{"image":"https://cdn.mos.cms.futurecdn.net/vChK6pTy3vN3KbYZ7UU7k3.jpg","post":"http://www.google.com"},
{"image":"https://cdn.mos.cms.futurecdn.net/vChK6pTy3vN3KbYZ7UU7k3.jpg","post":"http://www.google.com"},
{"image":"https://cdn.mos.cms.futurecdn.net/vChK6pTy3vN3KbYZ7UU7k3.jpg","post":"http://www.google.com"},
{"image":"https://cdn.mos.cms.futurecdn.net/vChK6pTy3vN3KbYZ7UU7k3.jpg","post":"http://www.google.com"},
{"image":"https://cdn.mos.cms.futurecdn.net/vChK6pTy3vN3KbYZ7UU7k3.jpg","post":"http://www.google.com"},
{"image":"https://cdn.mos.cms.futurecdn.net/vChK6pTy3vN3KbYZ7UU7k3.jpg","post":"http://www.google.com"},
{"image":"https://cdn.mos.cms.futurecdn.net/vChK6pTy3vN3KbYZ7UU7k3.jpg","post":"http://www.google.com"},
{"image":"https://cdn.mos.cms.futurecdn.net/vChK6pTy3vN3KbYZ7UU7k3.jpg","post":"http://www.google.com"},
{"image":"https://cdn.mos.cms.futurecdn.net/vChK6pTy3vN3KbYZ7UU7k3.jpg","post":"http://www.google.com"},
{"image":"https://cdn.mos.cms.futurecdn.net/vChK6pTy3vN3KbYZ7UU7k3.jpg","post":"http://www.google.com"},
{"image":"https://cdn.mos.cms.futurecdn.net/vChK6pTy3vN3KbYZ7UU7k3.jpg","post":"http://www.google.com"},
{"image":"https://cdn.mos.cms.futurecdn.net/vChK6pTy3vN3KbYZ7UU7k3.jpg","post":"http://www.google.com"},
{"image":"https://cdn.mos.cms.futurecdn.net/vChK6pTy3vN3KbYZ7UU7k3.jpg","post":"http://www.google.com"},
{"image":"https://cdn.mos.cms.futurecdn.net/vChK6pTy3vN3KbYZ7UU7k3.jpg","post":"http://www.google.com"},
{"image":"https://cdn.mos.cms.futurecdn.net/vChK6pTy3vN3KbYZ7UU7k3.jpg","post":"http://www.google.com"},
{"image":"https://cdn.mos.cms.futurecdn.net/vChK6pTy3vN3KbYZ7UU7k3.jpg","post":"http://www.google.com"},
{"image":"https://cdn.mos.cms.futurecdn.net/vChK6pTy3vN3KbYZ7UU7k3.jpg","post":"http://www.google.com"},
{"image":"https://cdn.mos.cms.futurecdn.net/vChK6pTy3vN3KbYZ7UU7k3.jpg","post":"http://www.google.com"},
{"image":"https://cdn.mos.cms.futurecdn.net/vChK6pTy3vN3KbYZ7UU7k3.jpg","post":"http://www.google.com"},
{"image":"https://cdn.mos.cms.futurecdn.net/vChK6pTy3vN3KbYZ7UU7k3.jpg","post":"http://www.google.com"},
{"image":"https://cdn.mos.cms.futurecdn.net/vChK6pTy3vN3KbYZ7UU7k3.jpg","post":"http://www.google.com"},
{"image":"https://cdn.mos.cms.futurecdn.net/vChK6pTy3vN3KbYZ7UU7k3.jpg","post":"http://www.google.com"},
{"image":"https://cdn.mos.cms.futurecdn.net/vChK6pTy3vN3KbYZ7UU7k3.jpg","post":"http://www.google.com"},
{"image":"https://cdn.mos.cms.futurecdn.net/vChK6pTy3vN3KbYZ7UU7k3.jpg","post":"http://www.google.com"},
{"image":"https://cdn.mos.cms.futurecdn.net/vChK6pTy3vN3KbYZ7UU7k3.jpg","post":"http://www.google.com"}]

1 Answer 1

1

You can use the following Lazy load component

    import React, { Component } from 'react'
    import PropTypes from 'prop-types'

    const LoaderComponent = () => (
      <div className='loader' >
        <div className='loader-text'>Loading...</div>
      </div>
    )

    class LazyLoadOnBody extends Component {
      constructor (props) {
        super(props)
        this.state = {
          loadMore: true,
          footer: this.props.footerHeight
        }
        this.onScroll = this.onScroll.bind(this)
      }

      componentDidMount () {
        window.addEventListener('scroll', this.onScroll, false)
      }

      componentWillReceiveProps (nextProps) {
        if (nextProps.currentTotal !== this.props.currentTotal) {
          this.setState({
            loadMore: true
          })
        }
      }

      componentWillUnmount () {
        window.removeEventListener('scroll', this.onScroll)
      }

      onScroll () {
        const windowHeight = 'innerHeight' in window ? window.innerHeight : document.documentElement.offsetHeight
        const { scrollHeight, offsetHeight } = document.body
        const { clientHeight } = document.documentElement
        const scrollHeightHtml = document.documentElement.scrollHeight
        const offsetHeightHtml = document.documentElement.offsetHeight
        const bodyHeight = Math.max(scrollHeight, offsetHeight)
        const docHeight = Math.max(bodyHeight, clientHeight, scrollHeightHtml, offsetHeightHtml)
        const windowBottom = windowHeight + window.pageYOffset
        const { total, currentTotal } = this.props
        if (windowBottom >= docHeight - 150 - this.state.footer && total > currentTotal) {
          if (this.state.loadMore) {
            this.props.loadMoreRows()
          }
          this.setState({
            loadMore: false
          })
        }
      }

      render () {
        return (
          <div className={this.props.className} >
            {this.props.children}
            {!this.state.loadMore ? this.props.loader : null}
          </div>
        )
      }
    }

    LazyLoadOnBody.propTypes = {
      total: PropTypes.number,
      currentTotal: PropTypes.number,
      loadMoreRows: PropTypes.func.isRequired,
      children: PropTypes.oneOfType([
        PropTypes.arrayOf(PropTypes.node),
        PropTypes.node
      ]),
      footerHeight: PropTypes.number,
      loader: PropTypes.node,
      className: PropTypes.string,
    }

    LazyLoadOnBody.defaultProps = {
      children: null,
      footerHeight: 0,
      loader: <LoaderComponent />,
      className: '',
      total: 0,
      currentTotal: 0
    }

    export default LazyLoadOnBody

and you can update your code accordingly

import React from 'react';
import LazyLoadBody from '../LazyLoadBody';

const Loader = () => (
  <div
    style={{
      display: 'flex',
      alignItems: 'center',
      justifyContent: 'center',
      height: 50
    }}
  >
    Loading...
  </div>
)

export default class FbGrid extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      apiResponse: [],
      page: 0
    }
  }
  componentDidMount() {
    this.callAPI();
  }

  callAPI() {
    fetch('/api/getList')
      .then(res => res.json())
      .then(res => this.setState({ apiResponse: res }))
  }

  loadMoreRows = () => {
    this.setState({ page: this.state.page + 1 })
  }

  render() {
    const { apiResponse, page } = this.state;

    if (!apiResponse) {
      return <p className="row">Loading...</p>
    }
    return (
      <div className="App">
        <div className="row">
          <br />{page}<br />{apiResponse.length}<div className="spacer" />
        </div>
        <LazyLoadOnBody loader={<Loader />} total={this.state.apiResponse.length} currentTotal={(page + 1) * 6} loadMoreRows={() => this.loadMoreRows()} >
          <div className='row' >
            {Object.values(apiResponse.slice(0, (page + 1) * 6)).map((value, i) => (
              <a key={i} target="_blank" rel="noopener noreferrer" href={value.post}>
                <img alt='' src={value.image} className="image" />
              </a>
            ))}
          </div>
        </LazyLoadOnBody>
      </div>
    )
  }
}
Sign up to request clarification or add additional context in comments.

2 Comments

Thank you for the answer. Though, I implemented this today and it gives me the same exact problem. It loads 6 when the page is rendered, I scroll down and it loads another 6, but then it stops. In this case it says "loading'". I don't understand why it won't the other 13 images. They are there. In my example code it loads all 25 with the test buttons I put in.
I wonder if the Object.values(apiResponse) JSON needs to be stored some other way. Although, like I said, the test button works on it just fine.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.