Quickstart

A quick dive into getting started with Lore

Step 6: Hide Deleted Tweets

In this step we'll hide tweets that have been deleted.

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

What's the problem?

Deleted tweets are still visible in the application, even though they don't exist on the server.

How do we solve this?

Filter them out of the data displayed in the Feed.

Exclude Deleted Tweets from Feed

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

// src/components/Feed.js
import PayloadStates from '../constants/PayloadStates';
...
render() {
  const { timestamp } = this.state;

  return (
    <div className="feed">
      ...
      <InfiniteScrollingList
        select={(getState) => {
          return getState('tweet.find', {
            where: {
              where: {
                createdAt: {
                  '<=': timestamp
                }
              }
            },
            pagination: {
              sort: 'createdAt DESC',
              page: 1
            },
            exclude: function(tweet) {
              return tweet.state === PayloadStates.DELETED;
            }
          });
        }}
        row={(tweet) => {
          return (
            <Tweet key={tweet.id || tweet.cid} tweet={tweet} />
          );
        }}
        refresh={(page, getState) => {
          return getState('tweet.find', _.defaultsDeep({
            exclude: function(tweet) {
              return tweet.state === PayloadStates.DELETED;
            }
          }, page.query));
        }}
        selectNextPage={(lastPage, getState) => {
          const lastPageNumber = lastPage.query.pagination.page;

          return getState('tweet.find', _.defaultsDeep({
            pagination: {
              page: lastPageNumber + 1
            },
            exclude: function(tweet) {
              return tweet.state === PayloadStates.DELETED;
            }
          }, lastPage.query));
        }}
        selectOther={(getState) => {
          return getState('tweet.all', {
            where: function(tweet) {
              const isOptimistic = !tweet.id;
              const isNew = moment(tweet.data.createdAt).diff(timestamp) > 0;
              return isOptimistic || isNew;
            },
            sortBy: function(model) {
              return -moment(model.data.createdAt).unix();
            },
            exclude: function(tweet) {
              return tweet.state === PayloadStates.DELETED;
            }
          });
        }}
      />
    </div>
  );
}

In the code above, we've added several exclude() functions to tell getState() what data we do NOT want included.

Visual Check-in

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

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 moment from 'moment';
import PayloadStates from '../constants/PayloadStates';
import InfiniteScrollingList from './InfiniteScrollingList';
import Tweet from './Tweet';

export default createReactClass({
  displayName: 'Feed',

  getInitialState() {
    return {
      timestamp: new Date().toISOString()
    };
  },

  render() {
    const { timestamp } = this.state;

    return (
      <div className="feed">
        <h2 className="title">
          Feed
        </h2>
        <InfiniteScrollingList
          select={(getState) => {
            return getState('tweet.find', {
              where: {
                where: {
                  createdAt: {
                    '<=': timestamp
                  }
                }
              },
              pagination: {
                sort: 'createdAt DESC',
                page: 1
              },
              exclude: function(tweet) {
                return tweet.state === PayloadStates.DELETED;
              }
            });
          }}
          row={(tweet) => {
            return (
              <Tweet key={tweet.id || tweet.cid} tweet={tweet} />
            );
          }}
          refresh={(page, getState) => {
            return getState('tweet.find', _.defaultsDeep({
              exclude: function(tweet) {
                return tweet.state === PayloadStates.DELETED;
              }
            }, page.query));
          }}
          selectNextPage={(lastPage, getState) => {
            const lastPageNumber = lastPage.query.pagination.page;

            return getState('tweet.find', _.defaultsDeep({
              pagination: {
                page: lastPageNumber + 1
              },
              exclude: function(tweet) {
                return tweet.state === PayloadStates.DELETED;
              }
            }, lastPage.query));
          }}
          selectOther={(getState) => {
            return getState('tweet.all', {
              where: function(tweet) {
                const isOptimistic = !tweet.id;
                const isNew = moment(tweet.data.createdAt).diff(timestamp) > 0;
                return isOptimistic || isNew;
              },
              sortBy: function(model) {
                return -moment(model.data.createdAt).unix();
              },
              exclude: function(tweet) {
                return tweet.state === PayloadStates.DELETED;
              }
            });
          }}
        />
      </div>
    );
  }

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

class Feed extends React.Component {

  constructor(props) {
    super(props);
    this.state = {
      timestamp: new Date().toISOString()
    };
  }

  render() {
    const { timestamp } = this.state;

    return (
      <div className="feed">
        <h2 className="title">
          Feed
        </h2>
        <InfiniteScrollingList
          select={(getState) => {
            return getState('tweet.find', {
              where: {
                where: {
                  createdAt: {
                    '<=': timestamp
                  }
                }
              },
              pagination: {
                sort: 'createdAt DESC',
                page: 1
              },
              exclude: function(tweet) {
                return tweet.state === PayloadStates.DELETED;
              }
            });
          }}
          row={(tweet) => {
            return (
              <Tweet key={tweet.id || tweet.cid} tweet={tweet} />
            );
          }}
          refresh={(page, getState) => {
            return getState('tweet.find', _.defaultsDeep({
              exclude: function(tweet) {
                return tweet.state === PayloadStates.DELETED;
              }
            }, page.query));
          }}
          selectNextPage={(lastPage, getState) => {
            const lastPageNumber = lastPage.query.pagination.page;

            return getState('tweet.find', _.defaultsDeep({
              pagination: {
                page: lastPageNumber + 1
              },
              exclude: function(tweet) {
                return tweet.state === PayloadStates.DELETED;
              }
            }, lastPage.query));
          }}
          selectOther={(getState) => {
            return getState('tweet.all', {
              where: function(tweet) {
                const isOptimistic = !tweet.id;
                const isNew = moment(tweet.data.createdAt).diff(timestamp) > 0;
                return isOptimistic || isNew;
              },
              sortBy: function(model) {
                return -moment(model.data.createdAt).unix();
              },
              exclude: function(tweet) {
                return tweet.state === PayloadStates.DELETED;
              }
            });
          }}
        />
      </div>
    );
  }

}

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

class Feed extends React.Component {

  constructor(props) {
    super(props);
    this.state = {
      timestamp: new Date().toISOString()
    };
  }

  render() {
    const { timestamp } = this.state;

    return (
      <div className="feed">
        <h2 className="title">
          Feed
        </h2>
        <InfiniteScrollingList
          select={(getState) => {
            return getState('tweet.find', {
              where: {
                where: {
                  createdAt: {
                    '<=': timestamp
                  }
                }
              },
              pagination: {
                sort: 'createdAt DESC',
                page: 1
              },
              exclude: function(tweet) {
                return tweet.state === PayloadStates.DELETED;
              }
            });
          }}
          row={(tweet) => {
            return (
              <Tweet key={tweet.id || tweet.cid} tweet={tweet} />
            );
          }}
          refresh={(page, getState) => {
            return getState('tweet.find', _.defaultsDeep({
              exclude: function(tweet) {
                return tweet.state === PayloadStates.DELETED;
              }
            }, page.query));
          }}
          selectNextPage={(lastPage, getState) => {
            const lastPageNumber = lastPage.query.pagination.page;

            return getState('tweet.find', _.defaultsDeep({
              pagination: {
                page: lastPageNumber + 1
              },
              exclude: function(tweet) {
                return tweet.state === PayloadStates.DELETED;
              }
            }, lastPage.query));
          }}
          selectOther={(getState) => {
            return getState('tweet.all', {
              where: function(tweet) {
                const isOptimistic = !tweet.id;
                const isNew = moment(tweet.data.createdAt).diff(timestamp) > 0;
                return isOptimistic || isNew;
              },
              sortBy: function(model) {
                return -moment(model.data.createdAt).unix();
              },
              exclude: function(tweet) {
                return tweet.state === PayloadStates.DELETED;
              }
            });
          }}
        />
      </div>
    );
  }

}

export default Feed;

Next Steps

In the next section we'll learn how to normalize an API response to reduce the number of network requests.