React render computations client side

Hi,

I’m currently developing an infinite scrolling system in order to display list of curated items in a react component, and I’m struggling with the rendering.

I currently subscribe in a withTracker block in order to fetch the necessary information :

const DEFAULT_LIMIT = 9;
const limit = new ReactiveVar(DEFAULT_LIMIT);

export default withTracker(props => {
  const searchSub = Meteor.subscribe("search", props.query, limit.get());
  const books = Book.find().fetch();

  return {
    searchSub: searchSub.ready(),
    books,
  };
})(BooksList);

When I reach the bottom of a page, a click on a button update the reactive var limit and thus the component rerender with the new items.
Here is my problem: how can I “hide” the rendering and just display the newly fetch elements from the updated subscription ?
Right now, my component entirely rerender, making the navigation hard because I’m back at the beginning of the list :confused:

Thanks for your help,

Store your positional state in a ReactiveDict, not in the component.

Or any detached state management system.

Yes, but this will provide a “teleportation” or “smooth” animation to get back to the old position and this is not a really good option in terms of UI/UX :confused:

Is there any solution to pass the newly fetched elements without having the whole components rerender and thus provide really smooth infinite scrolling ?

I do this in pages. I load 10 documents per page, and when scrolling to the bottom, load another page of 10. The state that controls how many pages to show is a HOC that sits on top of the paged list components. Each page can be memoized. So when 10 are added, only that one page is rerendered, rather than the entire list.

I think I see your point, but do you have a simple code example for this @captainn ?

I don’t have anything easily accessable atm, but it’s fairly straight forward. Each “Paged” component has an API that accepts an offset, which is used to grab data with through Mongo pagination API. Here’s a utility method from npdev:collections for example:


export const makePagedRun = (collection, query) => ({ limit = 5, offset = 0, order = -1, orderBy = 'createdAt', ...rest }) => (
  collection.find(query(rest), {
    limit,
    skip: offset,
    sort: {
      [orderBy]: order
    }
  }).fetch()
)

So whatever infinite scrolling method you use to determine when to load a new page would add a new page - I just use a counter on the pagination HOC:


  state = {
    pagesLoaded: 1,
    isLoading: false,
    noMoreDocs: false,
    numDocs: 0
  }

It’s a little messy - pagesLoaded actually tracks the number of displayed pages, and a new page is loaded by incrementing that value (I pause the infinite scroll checks while the next page is loading, etc.). It should probably be pagesDisplayed

Anyway, the component hierarchy is PaginationHoc -> PageOfDocs (which is withTracker HOC -> PageList -> Item).

Maybe it’s worth using an off the shelf solution? https://www.npmjs.com/package/react-infinite-scroller

1 Like

Where do you subscribe in this kind of thing ?
I think my main issue is because I subscribe once in the HOC :

const searchSub = Meteor.subscribe("search", props.query, limit.get());

So when the limit get updated, my subscription is updated as well, so the props I’m passing to the component is updated and thus the whole componed is updated.

So if the props change again, it will still rerender the whole thing right ?

You probably want to subscribe to each page separately, in the page component, not the main pagination component. Subscribing in Meteor is like loading. If you want to do it without subscriptions you’d need to use a method instead, but it would work similarly. Every load should happen in the HOC where the data is used.

So if you have 30 pages of data (300 documents), you’d end up with 30 subscriptions (each one limited to the offset and limit defined for the page). There are optimizations you can employ to reduce that overhead - such as when all the docs of a page scroll off the page, you can replace that with an empty div of a the same size, and kill the subscription (and recreate if scrolled back, etc.), but that’s a whole different ball of wax.

Yeah, I get that.

I was worried of having 30 subscriptions active in terms of performance, but if it seems okay I can just go with that :smile:

The overhead is mostly measured in the overall document count, I think. There may be some additional overhead from the pagination bounds in your queries though. But like I said, you can optimize the number of subscriptions out later (and gain some browser efficiency too), or even bypass them completely (that’s what I do for npdev:collections which loads over methods instead of pub/sub).

Yeah, as we are talking e-commerce app here, it’s highly unlikely that a client will ever load thousands of references in the page, and even if this case, I’ll probably just remove the subscription with an empty div if needed while he is away from the block.

Thanks a lot for your answer, it helps !