Quickstart

A quick dive into getting started with Lore

Step 2: Paginate the Tweets

In this step we'll update the Feed component to support pagination.

You can view the finished code for this step by checking out the pagination.2 branch of the completed project.

Paginating the API

The API supports pagination through a query parameter named page.

For example, if you navigate to the URL http://localhost:1337/tweets?page=1 you will see the first page of tweets, and if you navigate to the URL http://localhost:1337/tweets?page=2 you will see the second page.

In this step we'll be modify our API call to include this query parameter so that we can retrieve specific pages of data.

Pagination Strategy

To implement pagination on the client-side, we're going to be using a query parameter in the URL in order to determine which page of data to display, and we're going to call that query parameter page.

For example, navigating to the URL http://localhost:3000/?page=1 should display the first page of tweets in the Feed, and navigating to the URL http://localhost:3000/?page=2 should display the second.

Request Paginated Data

Open the Feed component and take a look at the connect decorator, which currently looks like this:

// src/components/Feed.js
connect(function(getState, props) {
  return {
    tweets: getState('tweet.find')
  };
})

Since we didn't provide any additional information to getState('tweet.find'), the API call that's produced is simply a network request to /tweets. But if we're going to use paginated data, we need network calls that look like /tweets?page=1.

To do that, update the connect call to look like this:

// src/components/Feed.js
export default connect(function(getState, props) {
  const { location } = props;

  return {
    tweets: getState('tweet.find', {
      pagination: {
        sort: 'createdAt DESC',
        page: location.query.page || '1'
      }
    })
  }
})(
createReactClass({
  ...
})
)
// src/components/Feed.js
class Feed extends React.Component {
 ...
}

export default connect(function(getState, props) {
  const { location } = props;

  return {
    tweets: getState('tweet.find', {
      pagination: {
        sort: 'createdAt DESC',
        page: location.query.page || '1'
      }
    })
  };
})(Feed);
// src/components/Feed.js
@connect(function(getState, props) {
  const { location } = props;

  return {
    tweets: getState('tweet.find', {
      pagination: {
        sort: 'createdAt DESC',
        page: location.query.page || '1'
      }
    })
  };
})
class Feed extends React.Component {
 ...
}

In the code above, we're first extracting location from props, which is provided by React Router and contains information like the current URL and any query parameters.

Then we're providing an object to getState('tweet.find') in order to be more specific about what question we want to ask the API.

The find part of tweet.find is actually the name of a blueprint that determines what options can be provided in the second argument to getState(). The same is true of tweet.byId, and it's actually the byId blueprint that requires there to be an id provided when using it.

You can read more about the available options for the find blueprint here.

Among the available options for the find blueprint is a pagination property that can be used to list query parameters that relate to pagination, such as page number, page size, ordering preferences, etc.

Within the pagination property we're providing two query parameters that we want sent to the API:

  • The first is sort, which controls how the data that's returned will be ordered. In this case we wanted the newest tweets to be returned first, so we've specified that we want the API to return data in descending order, based on the createdAt date of the tweet.

  • The second is page, which controls which page of data is returned. This is set set to the value of the page query parameter in the URL of the browser if one exists. If not, it defaults to the first page.

Try it Out

With this change in place, you should now be able to change what tweets are displayed based on the URL in the browser.

For example, if you navigate to http://localhost:3000/?page=1 you should see the first page of tweets, and navigating to http://localhost:3000/?page=2 should show you the second.

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 { connect } from 'lore-hook-connect';
import PayloadStates from '../constants/PayloadStates';
import Tweet from './Tweet';

export default connect(function(getState, props) {
  const { location } = props;

  return {
    tweets: getState('tweet.find', {
      pagination: {
        sort: 'createdAt DESC',
        page: location.query.page || '1'
      }
    })
  };
})(
createReactClass({
  displayName: 'Feed',

  propTypes: {
    tweets: PropTypes.object.isRequired
  },

  renderTweet(tweet) {
    return (
      <Tweet key={tweet.id} tweet={tweet} />
    );
  },

  render() {
    const { tweets } = this.props;

    if (tweets.state === PayloadStates.FETCHING) {
      return (
        <div className="feed">
          <h2 className="title">
            Feed
          </h2>
          <div className="loader"/>
        </div>
      );
    }

    return (
      <div className="feed">
        <h2 className="title">
          Feed
        </h2>
        <ul className="media-list tweets">
          {tweets.data.map(this.renderTweet)}
        </ul>
      </div>
    );
  }

})
);
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'lore-hook-connect';
import PayloadStates from '../constants/PayloadStates';
import Tweet from './Tweet';

class Feed extends React.Component {

  renderTweet(tweet) {
    return (
      <Tweet key={tweet.id} tweet={tweet} />
    );
  }

  render() {
    const { tweets } = this.props;

    if (tweets.state === PayloadStates.FETCHING) {
      return (
        <div className="feed">
          <h2 className="title">
            Feed
          </h2>
          <div className="loader"/>
        </div>
      );
    }

    return (
      <div className="feed">
        <h2 className="title">
          Feed
        </h2>
        <ul className="media-list tweets">
          {tweets.data.map(this.renderTweet)}
        </ul>
      </div>
    );
  }

}

Feed.propTypes = {
  tweets: PropTypes.object.isRequired
};

export default connect(function(getState, props) {
  const { location } = props;

  return {
    tweets: getState('tweet.find', {
      pagination: {
        sort: 'createdAt DESC',
        page: location.query.page || '1'
      }
    })
  };
})(Feed);
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'lore-hook-connect';
import PayloadStates from '../constants/PayloadStates';
import Tweet from './Tweet';

@connect(function(getState, props) {
  const { location } = props;

  return {
    tweets: getState('tweet.find', {
      pagination: {
        sort: 'createdAt DESC',
        page: location.query.page || '1'
      }
    })
  };
})
class Feed extends React.Component {

  static propTypes = {
    tweets: PropTypes.object.isRequired
  };

  renderTweet(tweet) {
    return (
      <Tweet key={tweet.id} tweet={tweet} />
    );
  }

  render() {
    const { tweets } = this.props;

    if (tweets.state === PayloadStates.FETCHING) {
      return (
        <div className="feed">
          <h2 className="title">
            Feed
          </h2>
          <div className="loader"/>
        </div>
      );
    }

    return (
      <div className="feed">
        <h2 className="title">
          Feed
        </h2>
        <ul className="media-list tweets">
          {tweets.data.map(this.renderTweet)}
        </ul>
      </div>
    );
  }

}

export default Feed;

Next Steps

Next step we're going to add pagination links.