Quickstart

A quick dive into getting started with Lore

Step 3: Convert the Feed

In this step we'll convert our Feed component to use Infinite Scrolling.

You can view the finished code for this step by checking out the infinite-scrolling.3 branch of the completed project.

Update the Feed Component

There are a lot of things that need to coordinate to get Infinite Scrolling to behave correctly, so we're going to be building our view up slowly, and explain a bit along the way.

To start, open the Feed component and modify it to look like this:

import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import InfiniteScrollingList from './InfiniteScrollingList';
import Tweet from './Tweet';

export default createReactClass({
  displayName: 'Feed',

  render() {
    return (
      <div className="feed">
        <h2 className="title">
          Feed
        </h2>
        <InfiniteScrollingList
          select={(getState) => {
            return getState('tweet.find', {
              pagination: {
                sort: 'createdAt DESC',
                page: 1
              }
            });
          }}
          row={(tweet) => {
            return (
              <Tweet key={tweet.id} tweet={tweet} />
            );
          }}
        />
      </div>
    );
  }

});
import React from 'react';
import PropTypes from 'prop-types';
import InfiniteScrollingList from './InfiniteScrollingList';
import Tweet from './Tweet';

class Feed extends React.Component {

  render() {
    return (
      <div className="feed">
        <h2 className="title">
          Feed
        </h2>
        <InfiniteScrollingList
          select={(getState) => {
            return getState('tweet.find', {
              pagination: {
                sort: 'createdAt DESC',
                page: 1
              }
            });
          }}
          row={(tweet) => {
            return (
              <Tweet key={tweet.id} tweet={tweet} />
            );
          }}
        />
      </div>
    );
  }

}

export default Feed;
import React from 'react';
import PropTypes from 'prop-types';
import InfiniteScrollingList from './InfiniteScrollingList';
import Tweet from './Tweet';

class Feed extends React.Component {

  render() {
    return (
      <div className="feed">
        <h2 className="title">
          Feed
        </h2>
        <InfiniteScrollingList
          select={(getState) => {
            return getState('tweet.find', {
              pagination: {
                sort: 'createdAt DESC',
                page: 1
              }
            });
          }}
          row={(tweet) => {
            return (
              <Tweet key={tweet.id} tweet={tweet} />
            );
          }}
        />
      </div>
    );
  }

}

export default Feed;

In the code above, we're providing two props to InfiniteScrollingList.

  • The first prop is called select, and we're using it to tell the list what data to render. It's essentially a version of connect, but exposed as a prop instead of a decorator. In this case, we're fetching the first page of tweets, sorted in descending order by their createdAt date.

  • The second prop is called row, and we're using it to tell the list how to render the data. It will be invoked for each tweet in the list, and whatever the function returns is what will be rendered for that tweet.

If you refresh the browser, you'll see the application renders, but it's stuck at a loading screen:

Why is this happening?

The reason the application is stuck at a loading screen is because the data being rendering is out of date. Even though the data has returned from the API, the list is still rendering the original data, back when the state was FETCHING.

We don't see this issue when using the connect decorator because that data is automatically refreshed every time the application re-renders. But that kind of always-up-to-date behavior is only possible when we're explicit about what data we want.

In this case, the only thing we're explicit about is the first page of data, which we provide via the select prop. The other pages are all derived, based on the page number, and since this component will be managing many pages of data, we're going to need to give it some help to know how to refresh the data for each of those pages.

Refresh the Data

To fix this, provide a prop to InfiniteScrollingList called refresh that looks like this:

// src/components/Feed.js
...
<InfiniteScrollingList
  select={(getState) => {
    return getState('tweet.find', {
      pagination: {
        sort: 'createdAt DESC',
        page: 1
      }
    });
  }}
  row={(tweet) => {
    return (
      <Tweet key={tweet.id} tweet={tweet} />
    );
  }}
  refresh={(page, getState) => {
    return getState('tweet.find', page.query);
  }}
/>
...

This method will invoked each time the application re-renders, for each page of data the list has. And because the query is automatically attached to every collection, we can simply re-use it, and fetch the latest version of that page.

With that change in place, the application is now rendering the first page of tweets.

Load More Pages

To load more tweets, import lodash and provide another prop called selectNextPage that will describe how to fetch the next page of tweets:

// src/components/Feed.js
import _ from 'lodash';
...
<InfiniteScrollingList
  select={(getState) => {
    return getState('tweet.find', {
      pagination: {
        sort: 'createdAt DESC',
        page: 1
      }
    });
  }}
  row={(tweet) => {
    return (
      <Tweet key={tweet.id} tweet={tweet} />
    );
  }}
  refresh={(page, getState) => {
    return getState('tweet.find', page.query);
  }}
  selectNextPage={(lastPage, getState) => {
    const lastPageNumber = lastPage.query.pagination.page;

    return getState('tweet.find', _.defaultsDeep({
      pagination: {
        page: lastPageNumber + 1
      }
    }, lastPage.query));
  }}
/>

This method will be invoked when the user presses the "Load More" button, and will be provided the last page of data. We then inspect the query for the lastPage to get the latest page number, and then iterate it by one to request the next page of tweets.

Refresh the browser, and you should now have a button that says "LoadMore" at the bottom of the tweets. Clicking this button will cause the next page to load, and if you continue to click it until there are no more pages of data, the button will disappear.

Visual Check-in

If everything went well, your application should now look like this.

Code Changes

Below is a list of files modified during this step.

src/components/Feed.js

import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import _ from 'lodash';
import InfiniteScrollingList from './InfiniteScrollingList';
import Tweet from './Tweet';

export default createReactClass({
  displayName: 'Feed',

  render() {
    return (
      <div className="feed">
        <h2 className="title">
          Feed
        </h2>
        <InfiniteScrollingList
          select={(getState) => {
            return getState('tweet.find', {
              pagination: {
                sort: 'createdAt DESC',
                page: 1
              }
            });
          }}
          row={(tweet) => {
            return (
              <Tweet key={tweet.id} tweet={tweet} />
            );
          }}
          refresh={(page, getState) => {
            return getState('tweet.find', page.query);
          }}
          selectNextPage={(lastPage, getState) => {
            const lastPageNumber = lastPage.query.pagination.page;

            return getState('tweet.find', _.defaultsDeep({
              pagination: {
                page: lastPageNumber + 1
              }
            }, lastPage.query));
          }}
        />
      </div>
    );
  }

});
import React from 'react';
import PropTypes from 'prop-types';
import _ from 'lodash';
import InfiniteScrollingList from './InfiniteScrollingList';
import Tweet from './Tweet';

class Feed extends React.Component {

  render() {
    return (
      <div className="feed">
        <h2 className="title">
          Feed
        </h2>
        <InfiniteScrollingList
          select={(getState) => {
            return getState('tweet.find', {
              pagination: {
                sort: 'createdAt DESC',
                page: 1
              }
            });
          }}
          row={(tweet) => {
            return (
              <Tweet key={tweet.id} tweet={tweet} />
            );
          }}
          refresh={(page, getState) => {
            return getState('tweet.find', page.query);
          }}
          selectNextPage={(lastPage, getState) => {
            const lastPageNumber = lastPage.query.pagination.page;

            return getState('tweet.find', _.defaultsDeep({
              pagination: {
                page: lastPageNumber + 1
              }
            }, lastPage.query));
          }}
        />
      </div>
    );
  }

}

export default Feed;
import React from 'react';
import PropTypes from 'prop-types';
import _ from 'lodash';
import InfiniteScrollingList from './InfiniteScrollingList';
import Tweet from './Tweet';

class Feed extends React.Component {

  render() {
    return (
      <div className="feed">
        <h2 className="title">
          Feed
        </h2>
        <InfiniteScrollingList
          select={(getState) => {
            return getState('tweet.find', {
              pagination: {
                sort: 'createdAt DESC',
                page: 1
              }
            });
          }}
          row={(tweet) => {
            return (
              <Tweet key={tweet.id} tweet={tweet} />
            );
          }}
          refresh={(page, getState) => {
            return getState('tweet.find', page.query);
          }}
          selectNextPage={(lastPage, getState) => {
            const lastPageNumber = lastPage.query.pagination.page;

            return getState('tweet.find', _.defaultsDeep({
              pagination: {
                page: lastPageNumber + 1
              }
            }, lastPage.query));
          }}
        />
      </div>
    );
  }

}

export default Feed;

Next Steps

Next we're going to clean up our code base bit..