Quickstart

A quick dive into getting started with Lore

Step 2: Create Tweet Component

In this step we're going to create a Tweet component and make our tweets look more attractive.

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

Create the Tweet Component

First we need to create the Tweet component. Run this command to have the CLI generate it for us:

lore generate component Tweet

Then update the code for that component to look like this:

import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';

export default createReactClass({
  displayName: 'Tweet',

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

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

    return (
      <li className="list-group-item tweet">
        <div className="image-container">
          <img
            className="img-circle avatar"
            src={'http://ssl.gstatic.com/images/icons/material/product/1x/avatar_circle_blue_120dp.png'} />
        </div>
        <div className="content-container">
          <h4 className="list-group-item-heading title">
            Nickname
          </h4>
          <h4 className="list-group-item-heading timestamp">
            {'- Timestamp'}
          </h4>
          <p className="list-group-item-text text">
            {'This is a quote from Chrono Trigger.'}
          </p>
        </div>
      </li>
    );
  }

});
import React from 'react';
import PropTypes from 'prop-types';

class Tweet extends React.Component {

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

    return (
      <li className="list-group-item tweet">
        <div className="image-container">
          <img
            className="img-circle avatar"
            src={'http://ssl.gstatic.com/images/icons/material/product/1x/avatar_circle_blue_120dp.png'} />
        </div>
        <div className="content-container">
          <h4 className="list-group-item-heading title">
            Nickname
          </h4>
          <h4 className="list-group-item-heading timestamp">
            {'- Timestamp'}
          </h4>
          <p className="list-group-item-text text">
            {'This is a quote from Chrono Trigger.'}
          </p>
        </div>
      </li>
    );
  }

}

Tweet.propTypes = {
  tweet: PropTypes.object.isRequired
};

export default Tweet;
import React from 'react';
import PropTypes from 'prop-types';

class Tweet extends React.Component {

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

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

    return (
      <li className="list-group-item tweet">
        <div className="image-container">
          <img
            className="img-circle avatar"
            src={'http://ssl.gstatic.com/images/icons/material/product/1x/avatar_circle_blue_120dp.png'} />
        </div>
        <div className="content-container">
          <h4 className="list-group-item-heading title">
            Nickname
          </h4>
          <h4 className="list-group-item-heading timestamp">
            {'- Timestamp'}
          </h4>
          <p className="list-group-item-text text">
            {'This is a quote from Chrono Trigger.'}
          </p>
        </div>
      </li>
    );
  }

}

export default Tweet;

Here we've added a propType declaring that this component expects to receive a tweet, and then we've updated the render() method to display it.

Use Tweet in Feed

With our Tweet component created, let’s use it in our Feed. Open the Feed component and update the renderTweet() method to look like this:

// src/components/Feed.js
import Tweet from './Tweet';
...
  renderTweet(tweet) {
    return (
      <Tweet key={tweet.id} tweet={tweet} />
    );
  },
...
// src/components/Feed.js
import Tweet from './Tweet';
...
  renderTweet(tweet) {
    return (
      <Tweet key={tweet.id} tweet={tweet} />
    );
  }
...
// src/components/Feed.js
import Tweet from './Tweet';
...
  renderTweet(tweet) {
    return (
      <Tweet key={tweet.id} tweet={tweet} />
    );
  }
...

Refresh the browser and your app should now look like this:

Update Tweet to Use Mock Data

While the Tweet component is now showing up in the Feed, it's currently showing some hard-coded data instead of the mock data we created previously. Let's change that.

Update the render() method of the Tweet component to look like this:

// src/components/Tweet.js
...
    render() {
      const { tweet } = this.props;

      return (
        <li className="list-group-item tweet">
          <div className="image-container">
            <img
              className="img-circle avatar"
              src={'http://ssl.gstatic.com/images/icons/material/product/1x/avatar_circle_blue_120dp.png'} />
          </div>
          <div className="content-container">
            <h4 className="list-group-item-heading title">
              Nickname
            </h4>
            <h4 className="list-group-item-heading timestamp">
              {'- ' + tweet.data.createdAt}
            </h4>
            <p className="list-group-item-text text">
              {tweet.data.text}
            </p>
          </div>
        </li>
      );
    }
  ...

Refresh the browser and you should now see the mock data being displayed in the Tweet:

Fix the Timestamp

That's a little better, but what's up with that ugly timestamp of 2018-04-24T05:10:49.382Z? That's not what we want; we want a clear statement like 3 days to show how old the Tweet is. Luckily we can easily fix that using a library called moment.

Moment is a date/time library for Javascript, and it's a great tool for converting timestamps to a more human-friendly format.

Install moment with this command:

npm install moment --save

After installing moment you may need to stop and restart the webpack development server in order for Webpack to see the new package.

Once moment is installed, import it into your Tweet component and update the render() method to look like this:

// src/components/Tweet.js
import moment from 'moment';

...
  render() {
    const { tweet } = this.props;
    const timestamp = moment(tweet.data.createdAt).fromNow().split(' ago')[0];

    return (
      <li className="list-group-item tweet">
        <div className="image-container">
          <img
            className="img-circle avatar"
            src={'http://ssl.gstatic.com/images/icons/material/product/1x/avatar_circle_blue_120dp.png'} />
        </div>
        <div className="content-container">
          <h4 className="list-group-item-heading title">
            Nickname
          </h4>
          <h4 className="list-group-item-heading timestamp">
            {'- ' + timestamp}
          </h4>
          <p className="list-group-item-text text">
            {tweet.data.text}
          </p>
        </div>
      </li>
    );
  }
...

Let's break down the statement moment(tweet.data.createdAt).fromNow().split(' ago')[0] to explain what's happening.

First, we're calling moment(tweet.data.createdAt) to convert the createdAt date into a moment object, which gives us access to utility methods.

Then we're appending fromNow() to the end of that statement to convert the timestamp from the format 2018-04-24T05:10:49.382Z into a human-readable string like 3 days ago.

And finally, while we could leave it at that, the word "ago" in the phrase "3 days ago" is understood from the context. So we're also going to remove it from the final timestamp by splitting the string at the " ago" part and only taking the first piece (converting "3 days ago" to simply "3 days").

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/Tweet.js

import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import moment from 'moment';

export default createReactClass({
  displayName: 'Tweet',

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

  render() {
    const { tweet } = this.props;
    const timestamp = moment(tweet.data.createdAt).fromNow().split(' ago')[0];

    return (
      <li className="list-group-item tweet">
        <div className="image-container">
          <img
            className="img-circle avatar"
            src={'http://ssl.gstatic.com/images/icons/material/product/1x/avatar_circle_blue_120dp.png'} />
        </div>
        <div className="content-container">
          <h4 className="list-group-item-heading title">
            Nickname
          </h4>
          <h4 className="list-group-item-heading timestamp">
            {'- ' + timestamp}
          </h4>
          <p className="list-group-item-text text">
            {tweet.data.text}
          </p>
        </div>
      </li>
    );
  }

});
import React from 'react';
import PropTypes from 'prop-types';
import moment from 'moment';

class Tweet extends React.Component {

  render() {
    const { tweet } = this.props;
    const timestamp = moment(tweet.data.createdAt).fromNow().split(' ago')[0];

    return (
      <li className="list-group-item tweet">
        <div className="image-container">
          <img
            className="img-circle avatar"
            src={'http://ssl.gstatic.com/images/icons/material/product/1x/avatar_circle_blue_120dp.png'} />
        </div>
        <div className="content-container">
          <h4 className="list-group-item-heading title">
            Nickname
          </h4>
          <h4 className="list-group-item-heading timestamp">
            {'- ' + timestamp}
          </h4>
          <p className="list-group-item-text text">
            {tweet.data.text}
          </p>
        </div>
      </li>
    );
  }

}

Tweet.propTypes = {
  tweet: PropTypes.object.isRequired
};

export default Tweet;
import React from 'react';
import PropTypes from 'prop-types';
import moment from 'moment';

class Tweet extends React.Component {

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

  render() {
    const { tweet } = this.props;
    const timestamp = moment(tweet.data.createdAt).fromNow().split(' ago')[0];

    return (
      <li className="list-group-item tweet">
        <div className="image-container">
          <img
            className="img-circle avatar"
            src={'http://ssl.gstatic.com/images/icons/material/product/1x/avatar_circle_blue_120dp.png'} />
        </div>
        <div className="content-container">
          <h4 className="list-group-item-heading title">
            Nickname
          </h4>
          <h4 className="list-group-item-heading timestamp">
            {'- ' + timestamp}
          </h4>
          <p className="list-group-item-text text">
            {tweet.data.text}
          </p>
        </div>
      </li>
    );
  }

}

export default Tweet;

src/components/Feed.js

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

export default 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;

    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 Tweet from './Tweet';

class Feed extends React.Component {

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

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

    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 Feed;
import React from 'react';
import PropTypes from 'prop-types';
import Tweet from './Tweet';

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;

    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 add some mock user data to finish the Tweet component.