Quickstart
A quick dive into getting started with Lore
A quick dive into getting started with Lore
In this step we'll convert our Feed component to use Infinite Scrolling.
You can view the finished code for this step by checking out the
infinite-scrolling.3
branch of the completed project.
There are a lot of things that need to coordinate to get Infinite Scrolling to behave correctly, so we're going to be building our view up slowly, and explain a bit along the way.
To start, open the Feed
component and modify it to look like this:
import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import InfiniteScrollingList from './InfiniteScrollingList';
import Tweet from './Tweet';
export default createReactClass({
displayName: 'Feed',
render() {
return (
<div className="feed">
<h2 className="title">
Feed
</h2>
<InfiniteScrollingList
select={(getState) => {
return getState('tweet.find', {
pagination: {
sort: 'createdAt DESC',
page: 1
}
});
}}
row={(tweet) => {
return (
<Tweet key={tweet.id} tweet={tweet} />
);
}}
/>
</div>
);
}
});
import React from 'react';
import PropTypes from 'prop-types';
import InfiniteScrollingList from './InfiniteScrollingList';
import Tweet from './Tweet';
class Feed extends React.Component {
render() {
return (
<div className="feed">
<h2 className="title">
Feed
</h2>
<InfiniteScrollingList
select={(getState) => {
return getState('tweet.find', {
pagination: {
sort: 'createdAt DESC',
page: 1
}
});
}}
row={(tweet) => {
return (
<Tweet key={tweet.id} tweet={tweet} />
);
}}
/>
</div>
);
}
}
export default Feed;
import React from 'react';
import PropTypes from 'prop-types';
import InfiniteScrollingList from './InfiniteScrollingList';
import Tweet from './Tweet';
class Feed extends React.Component {
render() {
return (
<div className="feed">
<h2 className="title">
Feed
</h2>
<InfiniteScrollingList
select={(getState) => {
return getState('tweet.find', {
pagination: {
sort: 'createdAt DESC',
page: 1
}
});
}}
row={(tweet) => {
return (
<Tweet key={tweet.id} tweet={tweet} />
);
}}
/>
</div>
);
}
}
export default Feed;
In the code above, we're providing two props to InfiniteScrollingList
.
The first prop is called select
, and we're using it to tell the list what data to render. It's essentially a version of connect
, but exposed as a prop instead of a decorator. In this case, we're fetching the first page of tweets, sorted in descending order by their createdAt
date.
The second prop is called row
, and we're using it to tell the list how to render the data. It will be invoked for each tweet in the list, and whatever the function returns is what will be rendered for that tweet.
If you refresh the browser, you'll see the application renders, but it's stuck at a loading screen:
The reason the application is stuck at a loading screen is because the data being rendering is out of date. Even though the data has returned from the API, the list is still rendering the original data, back when the state was FETCHING
.
We don't see this issue when using the connect
decorator because that data is automatically refreshed every time the application re-renders. But that kind of always-up-to-date behavior is only possible when we're explicit about what data we want.
In this case, the only thing we're explicit about is the first page of data, which we provide via the select
prop. The other pages are all derived, based on the page
number, and since this component will be managing many pages of data, we're going to need to give it some help to know how to refresh the data for each of those pages.
To fix this, provide a prop to InfiniteScrollingList
called refresh
that looks like this:
// src/components/Feed.js
...
<InfiniteScrollingList
select={(getState) => {
return getState('tweet.find', {
pagination: {
sort: 'createdAt DESC',
page: 1
}
});
}}
row={(tweet) => {
return (
<Tweet key={tweet.id} tweet={tweet} />
);
}}
refresh={(page, getState) => {
return getState('tweet.find', page.query);
}}
/>
...
This method will invoked each time the application re-renders, for each page of data the list has. And because the query
is automatically attached to every collection, we can simply re-use it, and fetch the latest version of that page.
With that change in place, the application is now rendering the first page of tweets.
To load more tweets, import lodash
and provide another prop called selectNextPage
that will describe how to fetch the next page of tweets:
// src/components/Feed.js
import _ from 'lodash';
...
<InfiniteScrollingList
select={(getState) => {
return getState('tweet.find', {
pagination: {
sort: 'createdAt DESC',
page: 1
}
});
}}
row={(tweet) => {
return (
<Tweet key={tweet.id} tweet={tweet} />
);
}}
refresh={(page, getState) => {
return getState('tweet.find', page.query);
}}
selectNextPage={(lastPage, getState) => {
const lastPageNumber = lastPage.query.pagination.page;
return getState('tweet.find', _.defaultsDeep({
pagination: {
page: lastPageNumber + 1
}
}, lastPage.query));
}}
/>
This method will be invoked when the user presses the "Load More" button, and will be provided the last page of data. We then inspect the query
for the lastPage
to get the latest page number, and then iterate it by one to request the next page of tweets.
Refresh the browser, and you should now have a button that says "LoadMore" at the bottom of the tweets. Clicking this button will cause the next page to load, and if you continue to click it until there are no more pages of data, the button will disappear.
If everything went well, your application should now look like this.
Below is a list of files modified during this step.
import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import _ from 'lodash';
import InfiniteScrollingList from './InfiniteScrollingList';
import Tweet from './Tweet';
export default createReactClass({
displayName: 'Feed',
render() {
return (
<div className="feed">
<h2 className="title">
Feed
</h2>
<InfiniteScrollingList
select={(getState) => {
return getState('tweet.find', {
pagination: {
sort: 'createdAt DESC',
page: 1
}
});
}}
row={(tweet) => {
return (
<Tweet key={tweet.id} tweet={tweet} />
);
}}
refresh={(page, getState) => {
return getState('tweet.find', page.query);
}}
selectNextPage={(lastPage, getState) => {
const lastPageNumber = lastPage.query.pagination.page;
return getState('tweet.find', _.defaultsDeep({
pagination: {
page: lastPageNumber + 1
}
}, lastPage.query));
}}
/>
</div>
);
}
});
import React from 'react';
import PropTypes from 'prop-types';
import _ from 'lodash';
import InfiniteScrollingList from './InfiniteScrollingList';
import Tweet from './Tweet';
class Feed extends React.Component {
render() {
return (
<div className="feed">
<h2 className="title">
Feed
</h2>
<InfiniteScrollingList
select={(getState) => {
return getState('tweet.find', {
pagination: {
sort: 'createdAt DESC',
page: 1
}
});
}}
row={(tweet) => {
return (
<Tweet key={tweet.id} tweet={tweet} />
);
}}
refresh={(page, getState) => {
return getState('tweet.find', page.query);
}}
selectNextPage={(lastPage, getState) => {
const lastPageNumber = lastPage.query.pagination.page;
return getState('tweet.find', _.defaultsDeep({
pagination: {
page: lastPageNumber + 1
}
}, lastPage.query));
}}
/>
</div>
);
}
}
export default Feed;
import React from 'react';
import PropTypes from 'prop-types';
import _ from 'lodash';
import InfiniteScrollingList from './InfiniteScrollingList';
import Tweet from './Tweet';
class Feed extends React.Component {
render() {
return (
<div className="feed">
<h2 className="title">
Feed
</h2>
<InfiniteScrollingList
select={(getState) => {
return getState('tweet.find', {
pagination: {
sort: 'createdAt DESC',
page: 1
}
});
}}
row={(tweet) => {
return (
<Tweet key={tweet.id} tweet={tweet} />
);
}}
refresh={(page, getState) => {
return getState('tweet.find', page.query);
}}
selectNextPage={(lastPage, getState) => {
const lastPageNumber = lastPage.query.pagination.page;
return getState('tweet.find', _.defaultsDeep({
pagination: {
page: lastPageNumber + 1
}
}, lastPage.query));
}}
/>
</div>
);
}
}
export default Feed;
Next we're going to clean up our code base bit..