Quickstart

A quick dive into getting started with Lore

Step 1: Create Load More Button

In this step we'll create the Load More button that we'll need for infinite scrolling.

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

Button Behavior

The first component we're going to create will be the LoadMoreButton. The user will click this button to load more tweets, and it will have three responsibilities:

  1. Display the text "Load More" if there are more tweets to load
  2. Display a loading experience if more tweets are being fetched
  3. Disappear from view if there are no more tweets to fetch.

Add NextPage to Collection Metadata

Let's start by expanding the metadata for a collection to know whether there is a "next page" of data to load.

To do that, take a look at the API response for any endpoint that returns a collection:

{
  data: [
    {...tweet...},
    {...tweet...}
  ],
  meta: {
    paginate: {
      currentPage: 1,
      nextPage: 2,
      prevPage: null,
      totalPages: 11,
      totalCount: 51,
      perPage: 5
    }
  }
}

There's a field in meta.paginate called nextPage, and this field will either contain the number of the next page of data or be null if there are no more pages to display.

To use that field in our application, we need to add it to be meta property of collections. To do that, open config/connections.js and update the parse() method for collections to look like this:

// config/connections.js
...
  parse: function(response) {
    this.meta = {
      totalCount: response.meta.paginate.totalCount,
      perPage: response.meta.paginate.perPage,
      nextPage: response.meta.paginate.nextPage
    };
    return response.data;
  }
...

With that change, we'll now be able to discover if there's a next page of data by inspecting tweets.meta.nextPage.

Create the Button

Next, create the button component by running the following command:

lore generate component LoadMoreButton

Then modify the file to look like this:

import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import PayloadStates from '../constants/PayloadStates';

export default createReactClass({
  displayName: 'LoadMoreButton',

  propTypes: {
    lastPage: PropTypes.object.isRequired,
    onLoadMore: PropTypes.func.isRequired,
    nextPageMetaField: PropTypes.string.isRequired
  },

  render() {
    const {
      lastPage,
      onLoadMore,
      nextPageMetaField
    } = this.props;

    if(lastPage.state === PayloadStates.FETCHING) {
      return (
        <div className="footer">
          <div className="loader"/>
        </div>
      );
    }

    if (!lastPage.meta[nextPageMetaField]) {
      return (
        <div className="footer"/>
      );
    }

    return (
      <div className="footer">
        <button className="btn btn-default btn-lg" onClick={onLoadMore}>
          Load More
        </button>
      </div>
    );
  }

});
import React from 'react';
import PropTypes from 'prop-types';
import PayloadStates from '../constants/PayloadStates';

class LoadMoreButton extends React.Component {

  render() {
    const {
      lastPage,
      onLoadMore,
      nextPageMetaField
    } = this.props;

    if(lastPage.state === PayloadStates.FETCHING) {
      return (
        <div className="footer">
          <div className="loader"/>
        </div>
      );
    }

    if (!lastPage.meta[nextPageMetaField]) {
      return (
        <div className="footer"/>
      );
    }

    return (
      <div className="footer">
        <button className="btn btn-default btn-lg" onClick={onLoadMore}>
          Load More
        </button>
      </div>
    );
  }
}

LoadMoreButton.propTypes = {
  lastPage: PropTypes.object.isRequired,
  onLoadMore: PropTypes.func.isRequired,
  nextPageMetaField: PropTypes.string.isRequired
};

export default LoadMoreButton;
import React from 'react';
import PropTypes from 'prop-types';
import PayloadStates from '../constants/PayloadStates';

class LoadMoreButton extends React.Component {

  static propTypes = {
    lastPage: PropTypes.object.isRequired,
    onLoadMore: PropTypes.func.isRequired,
    nextPageMetaField: PropTypes.string.isRequired
  };

  render() {
    const {
      lastPage,
      onLoadMore,
      nextPageMetaField
    } = this.props;

    if(lastPage.state === PayloadStates.FETCHING) {
      return (
        <div className="footer">
          <div className="loader"/>
        </div>
      );
    }

    if (!lastPage.meta[nextPageMetaField]) {
      return (
        <div className="footer"/>
      );
    }

    return (
      <div className="footer">
        <button className="btn btn-default btn-lg" onClick={onLoadMore}>
          Load More
        </button>
      </div>
    );
  }
}

export default LoadMoreButton;

Visual Check-in

If everything went well, your application should now look like this. Still exactly the same :)

Code Changes

Below is a list of files modified during this step.

config/connections.js

import auth from '../src/utils/auth';

export default {

  default: {

    apiRoot: 'http://localhost:1337',

    headers: function() {
      return {
        Authorization: `Bearer ${auth.getToken()}`
      };
    },

    collections: {
      properties: {
        parse: function(response) {
          this.meta = {
            totalCount: response.meta.paginate.totalCount,
            perPage: response.meta.paginate.perPage,
            nextPage: response.meta.paginate.nextPage
          };
          return response.data;
        }
      }
    }

  }
};

src/components/LoadMoreButton.js

import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import PayloadStates from '../constants/PayloadStates';

export default createReactClass({
  displayName: 'LoadMoreButton',

  propTypes: {
    lastPage: PropTypes.object.isRequired,
    onLoadMore: PropTypes.func.isRequired,
    nextPageMetaField: PropTypes.string.isRequired
  },

  render() {
    const {
      lastPage,
      onLoadMore,
      nextPageMetaField
    } = this.props;

    if(lastPage.state === PayloadStates.FETCHING) {
      return (
        <div className="footer">
          <div className="loader" />
        </div>
      );
    }

    if (!lastPage.meta[nextPageMetaField]) {
      return (
        <div className="footer" />
      );
    }

    return (
      <div className="footer">
        <button className="btn btn-default btn-lg" onClick={onLoadMore}>
          Load More
        </button>
      </div>
    );
  }

});
import React from 'react';
import PropTypes from 'prop-types';
import PayloadStates from '../constants/PayloadStates';

class LoadMoreButton extends React.Component {

  render() {
    const {
      lastPage,
      onLoadMore,
      nextPageMetaField
    } = this.props;

    if(lastPage.state === PayloadStates.FETCHING) {
      return (
        <div className="footer">
          <div className="loader" />
        </div>
      );
    }

    if (!lastPage.meta[nextPageMetaField]) {
      return (
        <div className="footer" />
      );
    }

    return (
      <div className="footer">
        <button className="btn btn-default btn-lg" onClick={onLoadMore}>
          Load More
        </button>
      </div>
    );
  }

}

LoadMoreButton.propTypes = {
  lastPage: PropTypes.object.isRequired,
  onLoadMore: PropTypes.func.isRequired,
  nextPageMetaField: PropTypes.string.isRequired
};

export default LoadMoreButton;
import React from 'react';
import PropTypes from 'prop-types';
import PayloadStates from '../constants/PayloadStates';

class LoadMoreButton extends React.Component {

  static propTypes = {
    lastPage: PropTypes.object.isRequired,
    onLoadMore: PropTypes.func.isRequired,
    nextPageMetaField: PropTypes.string.isRequired
  };

  render() {
    const {
      lastPage,
      onLoadMore,
      nextPageMetaField
    } = this.props;

    if(lastPage.state === PayloadStates.FETCHING) {
      return (
        <div className="footer">
          <div className="loader" />
        </div>
      );
    }

    if (!lastPage.meta[nextPageMetaField]) {
      return (
        <div className="footer" />
      );
    }

    return (
      <div className="footer">
        <button className="btn btn-default btn-lg" onClick={onLoadMore}>
          Load More
        </button>
      </div>
    );
  }
}

export default LoadMoreButton;

Next Steps

Next we'll create the second component we'll need for Infinite Scrolling.