Quickstart

A quick dive into getting started with Lore

Step 4: Display Loading Experience

In this step we're going to learn how to use the state property in the data structure, and display a loading experience while the list of tweets are being fetched.

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

The Problem

While it may happen too quickly to notice, there is a period of time before the list of tweets is displayed when the page simply says "Feed" with nothing underneath.

This happens because the application doesn't fetch the tweets until the first time the Feed component is rendered, and during that render cycle, the component doesn't have any data to render.

Showing a blank view is a bad user experience, so let's update our Feed component to display a "loader" while the tweets are being fetched.

The State Property

When Lore performs any actions on data (such as fetching, creating, updating or deleting it) the state property of that data is updated to reflect the action being performed.

For example, the first time our Feed component is rendered, it requests a collection of tweets using getState('tweet.find'). Since this data doesn't exist in the local store yet, the framework will invoke an action to fetch it.

What gets provided to the Feed component at that point is a tweets prop that looks like this:

{
  state: 'FETCHING',
  data: [],
  query: {}
}

Here the state property of the data has been set to FETCHING in order to notify you that the data is being fetched. Once the data returns, the state property will have a value of RESOLVED to let you know that the data has been fetched. And if an error occurs while fetching the data, the state property would be updated to ERROR_FETCHING.

To make it easier to refer to these states, there's a file located at src/constants/PayloadStates.js that contains all the states Lore might assign to data. If you open it, there will a commented out section that shows the default states used by the framework, which are these:

export default {
  INITIAL_STATE: 'INITIAL_STATE',

  RESOLVED   : 'RESOLVED',
  NOT_FOUND: 'NOT_FOUND',
  DELETED: 'DELETED',

  CREATING: 'CREATING',
  UPDATING: 'UPDATING',
  DELETING: 'DELETING',
  FETCHING: 'FETCHING',

  ERROR_CREATING: 'ERROR_CREATING',
  ERROR_UPDATING: 'ERROR_UPDATING',
  ERROR_DELETING: 'ERROR_DELETING',
  ERROR_FETCHING: 'ERROR_FETCHING'
};

Let's use this file to create our loading experience.

You can learn more about this file here.

Add the Loading Experience

Open the Feed component and import PayloadStates. Then update the render() method to display a loading experience when the tweets are being fetched, like this:

// src/components/Feed.js
...
import PayloadStates from '../constants/PayloadStates';
...

  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>
    );
  }

...

Here we've added a check if the tweet.state property is equal to PayloadStates.FETCHING, and if it is, then we're rendering our loading experience.

Refresh the browser and you might see an animated "loader" flash on the screen right before the tweets are rendered. But if you can't see it (and you probably can't) don't worry; later we'll connect to a real API server that has an artificial delay, and you'll definitely see it then.

Visual Check-in

If everything went well, your application should now look like this when the Feed is loading:

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) {
  return {
    tweets: getState('tweet.find')
  };
})(
createReactClass({
  displayName: 'Feed',

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

  getDefaultProps() {
    const tweet = {
      id: 1,
      cid: 'c1',
      state: 'RESOLVED',
      data: {
        id: 1,
        userId: 1,
        text: 'Nothing can beat science!',
        createdAt: '2018-04-24T05:10:49.382Z'
      }
    };

    return {
      tweets: {
        state: 'RESOLVED',
        data: [tweet]
      }
    };
  },

  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
};

Feed.defaultProps = (function() {
  const tweet = {
    id: 1,
    cid: 'c1',
    state: 'RESOLVED',
    data: {
      id: 1,
      userId: 1,
      text: 'Nothing can beat science!',
      createdAt: '2018-04-24T05:10:49.382Z'
    }
  };

  return {
    tweets: {
      state: 'RESOLVED',
      data: [tweet]
    }
  };
})();

export default connect(function(getState, props) {
  return {
    tweets: getState('tweet.find')
  };
})(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) {
  return {
    tweets: getState('tweet.find')
  };
})
class Feed extends React.Component {

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

  static defaultProps = (function() {
    const tweet = {
      id: 1,
      cid: 'c1',
      state: 'RESOLVED',
      data: {
        id: 1,
        userId: 1,
        text: 'Nothing can beat science!',
        createdAt: '2018-04-24T05:10:49.382Z'
      }
    };

    return {
      tweets: {
        state: 'RESOLVED',
        data: [tweet]
      }
    };
  })();

  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 we're going to fetch the user for each tweet.