Infinite scroll in Meteor + React

I’m using Meteor with React. I cannot implement infinite scrolling. I have tried different npm and Meteor packages and read all other related topics, but it’s still not working. “createContainer” subscribes to the whole dataset of “links”, which is then rendered in the app. How can I subscribe to just 9 entries and load more whenever a user is at the end of the page?
I have spent the whole day on it, please help me!

class DealsList extends Component {


    renderList() {
        return this.props.links.map(link => {
        const url = `/travels/${link._id}`;
        
        return (
            <div className="col-md-4" key={link._id}>
            <Link  to={url} id={link._id}>
                <div className="thumbnail">
                
                <div className="imageProp">
                    <div className="caption readMore">SHOW DEAL <span className="glyphicon glyphicon-chevron-right" aria-hidden="true"></span></div>
                    <img src={link.image} alt="Just another travel deal" />
                </div>
                
                <div className="caption">
                    <h4>{link.title}</h4>
                    {/*<p className="card-description"><strong>Bootstrap Thumbnail</strong> Customization Example. Here are customized <strong>bootstrap cards</strong>. We just apply some box shadow and remove border radius.</p>
                    <p><a href="#" className="btn btn-primary" role="button">Button</a></p>*/}
                </div>
            
                </div>
            </Link>
            </div>
        
        );
        });
    }


    render() {

        return (
          <div> 
          <FirstCarousel />
          <div className="container-fluid containerPadding cards-row">
              <div className="row">

              <div className="col-lg-12">
                  <div className="row grid">
                      {this.renderList()}
                  </div>
              </div>

              </div>
              
          </div>
          </div>
        );
    }
}



export default createContainer(() => {

Meteor.subscribe('links');

return { links: Links.find({}).fetch() };
}, DealsList);

I use the following pattern for react infinite scroll with createContainer. The idea is to cross check the subscription status and current count and have the component not update.

In your production code, you might want to implement this as a higher order component(HoC) and also implement collection count subscription so that you hide the load more button when the number of docs on the screen reaches the collection’s recordset total.

import { Meteor } from 'meteor/meteor';
import { ReactiveVar } from 'meteor/reactive-var';
import React, { Component } from 'react';
import { createContainer } from 'meteor/react-meteor-data';

const DEFAULT_LIMIT = 10
const LIMIT_INCREMENT = 10
const limit = new ReactiveVar(DEFAULT_LIMIT)

export default createContainer(props => {
  const subscriptionsReady = [
    Meteor.subscribe('foo', {limit: limit.get()}),
  ].every(subscription => subscription.ready())

  const cursor = Foo.find({}, {
    sort: {bar: 1},
    limit: limit.get(),
  })

  return {
    subscriptionsLoading: !subscriptionsReady,
    records: cursor && cursor.fetch(),
    count: cursor && cursor.count(),
  }
}, class extends Component {
  constructor (props) {
    super(props)
    limit.set(DEFAULT_LIMIT)
  }

  shouldComponentUpdate (nextProps, nextState) {
    const {subscriptionsLoading, count} = nextProps
    if (!subscriptionsLoading && count === 0) {
      return true
    }
    return count > 0
  }

  loadMore = (e) => {
    if (e) e.preventDefault()
    limit.set(limit.get() + LIMIT_INCREMENT)
  }

  render () {
    const {
      subscriptionsLoading,
      records,
      count,
    } = this.props

    return <div>
      {
        records.length === 0
          ? !subscriptionsLoading && <div>Nothing to show</div>
          : <ul>records.map(record => <li key={record._id}>{record.foo}</li>)</ul>
      }
      {
        subscriptionsLoading && <div>Loading...</div>
      }
      {
        !subscriptionsLoading && count >= DEFAULT_LIMIT && <button onClick={this.loadMore}>Load More</button>
      }
    </div>;
  }
})
2 Likes

I think you need to update the subscription limit dynamically… take a look at here, not react but might give you an idea.

I understand the idea behind it. I just don’t know how to update the limit dynamically for my subscription!

Create a publication that accepts a limit parameter like so:

const DEFAULT_LIMIT = 10
Meteor.publish('foo', function(options) {
  // in production code, you should validate the options param to be a proper input for security
  const { limit = DEFAULT_LIMIT } = options;
  return [
    Foo.find({}, {
      sort: {bar: 1},
      limit,
    }),
  ];
}

Hey, whenever I use your code for the infinite scroll, I get the following error:

While building for web.browser: imports/ui/pages/deals_list.js:43:2: /imports/ui/pages/deals_list.js: Missing class properties transform.

Line 43 is:

 loadMore = (e) => {

I have tried updating Meteor (to 1.4.4.3), meteor packages, npm packages, but the error doesn’t go away.

That’s something you can fix using various approaches. You can find them all in this forum.

https://forums.meteor.com/search?q=missing%20class%20properties%20transform

Choose the one best fits your usecase.

I successfully implemented the “Load More” button and new items are loading everytime I press it, thank you very much. There is just one problem left. Each of the items on the list has a title and a link attributes. Whenever I visit the link (item page) and then go back to the list, the “limit” state is not preserved and I can again see only 10 items. I would like to track the “limit” variable over the app and if the person goes to an item page and then goes back - he would see the same amount of items he saw before entering that particular item. Did you get my point? Could you please show me an example of how could I implement this?

That means you need to store the limit information in a global state store. The code above creates a local store using a reactive var.

What you can alternatively do is, set up a rectivevar/reactivedict/session/localstorage/minimongo/redux/mobx (whatever suits you best) store globally and read/write the accrued limit into that store.

And of course, since you may very well have multiple lists, its best that you store the latest limit information per each list to avoid leaking one list’s limit status to others.

As for how to set up a global store, there are tons of threads in this forum (and on the meteor guide, numerous blog posts and stack overflow) that describe how people have done that using any of those alternatives (and more) that I’ve listed.

1 Like