Quickstart
A quick dive into getting started with Lore
A quick dive into getting started with Lore
In this step we'll create the List component we'll need for infinite scrolling.
You can view the finished code for this step by checking out the
infinite-scrolling.2
branch of the completed project.
The second component we'll create will be called InfiniteScrollingList
, and it will be responsible for displaying our list of tweets, as well as merging all the pages of data into a single array.
Create the component by running the following command:
lore generate component InfiniteScrollingList
Then modify the file to look like this:
import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import _ from 'lodash';
import { getState } from 'lore-hook-connect';
import PayloadStates from '../constants/PayloadStates';
import LoadMoreButton from './LoadMoreButton';
export default createReactClass({
displayName: 'InfiniteScrollingList',
propTypes: {
row: PropTypes.func.isRequired,
select: PropTypes.func.isRequired,
selectNextPage: PropTypes.func,
refresh: PropTypes.func,
selectOther: PropTypes.func,
exclude: PropTypes.func
},
getDefaultProps() {
return {
exclude: function(model) {
return false;
}
};
},
getInitialState() {
return {
other: null,
pages: []
};
},
// fetch first page
componentWillMount() {
const { select, selectOther } = this.props;
const nextState = this.state;
nextState.pages.push(select(getState));
if (selectOther) {
nextState.other = selectOther(getState);
}
this.setState(nextState);
},
// refresh data in all pages
componentWillReceiveProps(nextProps) {
const { refresh, selectOther } = this.props;
const { pages } = this.state;
const nextState = {};
if (refresh) {
nextState.pages = pages.map(function(page) {
return refresh(page, getState);
});
}
if (selectOther) {
nextState.other = selectOther(getState);
}
this.setState(nextState);
},
onLoadMore() {
const { selectNextPage } = this.props;
const { pages } = this.state;
const lastPage = pages[pages.length - 1];
pages.push(selectNextPage(lastPage, getState));
this.setState({
pages: pages
});
},
render() {
const { row, exclude, selectNextPage } = this.props;
const { pages, other } = this.state;
const numberOfPages = pages.length;
const firstPage = pages[0];
const lastPage = pages[pages.length - 1];
// if we only have one page, and it's fetching, then it's the initial
// page load so let the user know we're loading the data
if (numberOfPages === 1 && lastPage.state === PayloadStates.FETCHING) {
return (
<div className="loader" />
);
}
return (
<div>
<ul className="media-list tweets">
{other ? other.data.map(row) : null}
{_.flatten(pages.map((models) => {
return _.filter(models.data, (model) => {
return !exclude(model);
}).map(row);
}))}
</ul>
{selectNextPage ? (
<LoadMoreButton
lastPage={lastPage}
onLoadMore={this.onLoadMore}
nextPageMetaField="nextPage"
/>
) : null}
</div>
);
}
});
import React from 'react';
import PropTypes from 'prop-types';
import _ from 'lodash';
import { getState } from 'lore-hook-connect';
import PayloadStates from '../constants/PayloadStates';
import LoadMoreButton from './LoadMoreButton';
class InfiniteScrollingList extends React.Component {
constructor(props) {
super(props);
// set initial state
this.state = {
other: null,
pages: []
};
// bind custom methods
this.onLoadMore = this.onLoadMore.bind(this);
}
// fetch first page
componentWillMount() {
const { select, selectOther } = this.props;
const nextState = this.state;
nextState.pages.push(select(getState));
if (selectOther) {
nextState.other = selectOther(getState);
}
this.setState(nextState);
}
// refresh data in all pages
componentWillReceiveProps(nextProps) {
const { refresh, selectOther } = this.props;
const { pages } = this.state;
const nextState = {};
if (refresh) {
nextState.pages = pages.map(function(page) {
return refresh(page, getState);
});
}
if (selectOther) {
nextState.other = selectOther(getState);
}
this.setState(nextState);
}
onLoadMore() {
const { selectNextPage } = this.props;
const { pages } = this.state;
const lastPage = pages[pages.length - 1];
pages.push(selectNextPage(lastPage, getState));
this.setState({
pages: pages
});
}
render() {
const { row, exclude, selectNextPage } = this.props;
const { pages, other } = this.state;
const numberOfPages = pages.length;
const firstPage = pages[0];
const lastPage = pages[pages.length - 1];
// if we only have one page, and it's fetching, then it's the initial
// page load so let the user know we're loading the data
if (numberOfPages === 1 && lastPage.state === PayloadStates.FETCHING) {
return (
<div className="loader" />
);
}
return (
<div>
<ul className="media-list tweets">
{other ? other.data.map(row) : null}
{_.flatten(pages.map((models) => {
return _.filter(models.data, (model) => {
return !exclude(model);
}).map(row);
}))}
</ul>
{selectNextPage ? (
<LoadMoreButton
lastPage={lastPage}
onLoadMore={this.onLoadMore}
nextPageMetaField="nextPage"
/>
) : null}
</div>
);
}
}
InfiniteScrollingList.propTypes = {
row: PropTypes.func.isRequired,
select: PropTypes.func.isRequired,
selectNextPage: PropTypes.func,
refresh: PropTypes.func,
selectOther: PropTypes.func,
exclude: PropTypes.func
};
InfiniteScrollingList.defaultProps = {
exclude: function(model) {
return false;
}
};
export default InfiniteScrollingList;
import React from 'react';
import PropTypes from 'prop-types';
import _ from 'lodash';
import { getState } from 'lore-hook-connect';
import PayloadStates from '../constants/PayloadStates';
import LoadMoreButton from './LoadMoreButton';
class InfiniteScrollingList extends React.Component {
static propTypes = {
row: PropTypes.func.isRequired,
select: PropTypes.func.isRequired,
selectNextPage: PropTypes.func,
refresh: PropTypes.func,
selectOther: PropTypes.func,
exclude: PropTypes.func
};
static defaultProps = {
exclude: function(model) {
return false;
}
};
constructor(props) {
super(props);
// set initial state
this.state = {
other: null,
pages: []
};
// bind custom methods
this.onLoadMore = this.onLoadMore.bind(this);
}
// fetch first page
componentWillMount() {
const { select, selectOther } = this.props;
const nextState = this.state;
nextState.pages.push(select(getState));
if (selectOther) {
nextState.other = selectOther(getState);
}
this.setState(nextState);
}
// refresh data in all pages
componentWillReceiveProps(nextProps) {
const { refresh, selectOther } = this.props;
const { pages } = this.state;
const nextState = {};
if (refresh) {
nextState.pages = pages.map(function(page) {
return refresh(page, getState);
});
}
if (selectOther) {
nextState.other = selectOther(getState);
}
this.setState(nextState);
}
onLoadMore() {
const { selectNextPage } = this.props;
const { pages } = this.state;
const lastPage = pages[pages.length - 1];
pages.push(selectNextPage(lastPage, getState));
this.setState({
pages: pages
});
}
render() {
const { row, exclude, selectNextPage } = this.props;
const { pages, other } = this.state;
const numberOfPages = pages.length;
const firstPage = pages[0];
const lastPage = pages[pages.length - 1];
// if we only have one page, and it's fetching, then it's the initial
// page load so let the user know we're loading the data
if (numberOfPages === 1 && lastPage.state === PayloadStates.FETCHING) {
return (
<div className="loader" />
);
}
return (
<div>
<ul className="media-list tweets">
{other ? other.data.map(row) : null}
{_.flatten(pages.map((models) => {
return _.filter(models.data, (model) => {
return !exclude(model);
}).map(row);
}))}
</ul>
{selectNextPage ? (
<LoadMoreButton
lastPage={lastPage}
onLoadMore={this.onLoadMore}
nextPageMetaField="nextPage"
/>
) : null}
</div>
);
}
}
export default InfiniteScrollingList;
The component above is pretty generic. The props that start with select*
are functions that we'll be providing to describe what data we want to display, and the row
prop is a function that we'll provide to tell the List how to render each item in the array.
If everything went well, your application should now look like this. Still exactly the same :)
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 { getState } from 'lore-hook-connect';
import PayloadStates from '../constants/PayloadStates';
import LoadMoreButton from './LoadMoreButton';
export default createReactClass({
displayName: 'InfiniteScrollingList',
propTypes: {
row: PropTypes.func.isRequired,
select: PropTypes.func.isRequired,
selectNextPage: PropTypes.func,
refresh: PropTypes.func,
selectOther: PropTypes.func,
exclude: PropTypes.func
},
getDefaultProps() {
return {
exclude: function(model) {
return false;
}
};
},
getInitialState() {
return {
other: null,
pages: []
};
},
// fetch first page
componentWillMount() {
const { select, selectOther } = this.props;
const nextState = this.state;
nextState.pages.push(select(getState));
if (selectOther) {
nextState.other = selectOther(getState);
}
this.setState(nextState);
},
// refresh data in all pages
componentWillReceiveProps(nextProps) {
const { refresh, selectOther } = this.props;
const { pages } = this.state;
const nextState = {};
if (refresh) {
nextState.pages = pages.map(function(page) {
return refresh(page, getState);
});
}
if (selectOther) {
nextState.other = selectOther(getState);
}
this.setState(nextState);
},
onLoadMore() {
const { selectNextPage } = this.props;
const { pages } = this.state;
const lastPage = pages[pages.length - 1];
pages.push(selectNextPage(lastPage, getState));
this.setState({
pages: pages
});
},
render() {
const { row, exclude, selectNextPage } = this.props;
const { pages, other } = this.state;
const numberOfPages = pages.length;
const firstPage = pages[0];
const lastPage = pages[pages.length - 1];
// if we only have one page, and it's fetching, then it's the initial
// page load so let the user know we're loading the data
if (numberOfPages === 1 && lastPage.state === PayloadStates.FETCHING) {
return (
<div className="loader" />
);
}
return (
<div>
<ul className="media-list tweets">
{other ? other.data.map(row) : null}
{_.flatten(pages.map((models) => {
return _.filter(models.data, (model) => {
return !exclude(model);
}).map(row);
}))}
</ul>
{selectNextPage ? (
<LoadMoreButton
lastPage={lastPage}
onLoadMore={this.onLoadMore}
nextPageMetaField="nextPage"
/>
) : null}
</div>
);
}
});
import React from 'react';
import PropTypes from 'prop-types';
import _ from 'lodash';
import { getState } from 'lore-hook-connect';
import PayloadStates from '../constants/PayloadStates';
import LoadMoreButton from './LoadMoreButton';
class InfiniteScrollingList extends React.Component {
constructor(props) {
super(props);
// set initial state
this.state = {
other: null,
pages: []
};
// bind custom methods
this.onLoadMore = this.onLoadMore.bind(this);
}
// fetch first page
componentWillMount() {
const { select, selectOther } = this.props;
const nextState = this.state;
nextState.pages.push(select(getState));
if (selectOther) {
nextState.other = selectOther(getState);
}
this.setState(nextState);
}
// refresh data in all pages
componentWillReceiveProps(nextProps) {
const { refresh, selectOther } = this.props;
const { pages } = this.state;
const nextState = {};
if (refresh) {
nextState.pages = pages.map(function(page) {
return refresh(page, getState);
});
}
if (selectOther) {
nextState.other = selectOther(getState);
}
this.setState(nextState);
}
onLoadMore() {
const { selectNextPage } = this.props;
const { pages } = this.state;
const lastPage = pages[pages.length - 1];
pages.push(selectNextPage(lastPage, getState));
this.setState({
pages: pages
});
}
render() {
const { row, exclude, selectNextPage } = this.props;
const { pages, other } = this.state;
const numberOfPages = pages.length;
const firstPage = pages[0];
const lastPage = pages[pages.length - 1];
// if we only have one page, and it's fetching, then it's the initial
// page load so let the user know we're loading the data
if (numberOfPages === 1 && lastPage.state === PayloadStates.FETCHING) {
return (
<div className="loader" />
);
}
return (
<div>
<ul className="media-list tweets">
{other ? other.data.map(row) : null}
{_.flatten(pages.map((models) => {
return _.filter(models.data, (model) => {
return !exclude(model);
}).map(row);
}))}
</ul>
{selectNextPage ? (
<LoadMoreButton
lastPage={lastPage}
onLoadMore={this.onLoadMore}
nextPageMetaField="nextPage"
/>
) : null}
</div>
);
}
}
InfiniteScrollingList.propTypes = {
row: PropTypes.func.isRequired,
select: PropTypes.func.isRequired,
selectNextPage: PropTypes.func,
refresh: PropTypes.func,
selectOther: PropTypes.func,
exclude: PropTypes.func
};
InfiniteScrollingList.defaultProps = {
exclude: function(model) {
return false;
}
};
export default InfiniteScrollingList;
import React from 'react';
import PropTypes from 'prop-types';
import _ from 'lodash';
import { getState } from 'lore-hook-connect';
import PayloadStates from '../constants/PayloadStates';
import LoadMoreButton from './LoadMoreButton';
class InfiniteScrollingList extends React.Component {
static propTypes = {
row: PropTypes.func.isRequired,
select: PropTypes.func.isRequired,
selectNextPage: PropTypes.func,
refresh: PropTypes.func,
selectOther: PropTypes.func,
exclude: PropTypes.func
};
static defaultProps = {
exclude: function(model) {
return false;
}
};
constructor(props) {
super(props);
// set initial state
this.state = {
other: null,
pages: []
};
// bind custom methods
this.onLoadMore = this.onLoadMore.bind(this);
}
// fetch first page
componentWillMount() {
const { select, selectOther } = this.props;
const nextState = this.state;
nextState.pages.push(select(getState));
if (selectOther) {
nextState.other = selectOther(getState);
}
this.setState(nextState);
}
// refresh data in all pages
componentWillReceiveProps(nextProps) {
const { refresh, selectOther } = this.props;
const { pages } = this.state;
const nextState = {};
if (refresh) {
nextState.pages = pages.map(function(page) {
return refresh(page, getState);
});
}
if (selectOther) {
nextState.other = selectOther(getState);
}
this.setState(nextState);
}
onLoadMore() {
const { selectNextPage } = this.props;
const { pages } = this.state;
const lastPage = pages[pages.length - 1];
pages.push(selectNextPage(lastPage, getState));
this.setState({
pages: pages
});
}
render() {
const { row, exclude, selectNextPage } = this.props;
const { pages, other } = this.state;
const numberOfPages = pages.length;
const firstPage = pages[0];
const lastPage = pages[pages.length - 1];
// if we only have one page, and it's fetching, then it's the initial
// page load so let the user know we're loading the data
if (numberOfPages === 1 && lastPage.state === PayloadStates.FETCHING) {
return (
<div className="loader" />
);
}
return (
<div>
<ul className="media-list tweets">
{other ? other.data.map(row) : null}
{_.flatten(pages.map((models) => {
return _.filter(models.data, (model) => {
return !exclude(model);
}).map(row);
}))}
</ul>
{selectNextPage ? (
<LoadMoreButton
lastPage={lastPage}
onLoadMore={this.onLoadMore}
nextPageMetaField="nextPage"
/>
) : null}
</div>
);
}
}
export default InfiniteScrollingList;
Next we'll convert the Feed to use Infinite Scrolling.