Skip to content

Commit

Permalink
Add lazy loading functionality (#87)
Browse files Browse the repository at this point in the history
* Attempt to add in fixes suggested per other OSS modules

* Add semi colon

* First run for balance columns

* Rename function to findMinIndex

* Simplify array creation syntax

* Refactor balance

* Refactor priority and fix data update changes

* Update example to reference project dir

* Update masonry

* Minor stylistic changes, comment test

* Add updated snapshot

* Add append data

* Fix delay call reach

* Update snapshot for lazy load
  • Loading branch information
brh55 authored Apr 9, 2018
1 parent 2893974 commit 0fd1ba5
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 45 deletions.
2 changes: 2 additions & 0 deletions __tests__/__snapshots__/masonry.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ exports[`SNAPSHOT: All functionality should match prev snapshot 1`] = `
}
}
enableEmptySections={true}
onEndReached={[Function]}
onEndReachedThreshold={25}
refreshControl={undefined}
removeClippedSubviews={false}
renderRow={[Function]}
Expand Down
75 changes: 48 additions & 27 deletions components/Masonry.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ export default class Masonry extends Component {
customImageProps: PropTypes.object,
spacing: PropTypes.number,
priority: PropTypes.string,
refreshControl: PropTypes.element
refreshControl: PropTypes.element,
onEndReached: PropTypes.func,
onEndReachedThreshold: PropTypes.number
};

static defaultProps = {
Expand All @@ -45,7 +47,10 @@ export default class Masonry extends Component {
sorted: false,
imageContainerStyle: {},
spacing: 1,
priority: 'order'
priority: 'order',
// no-op function
onEndReached: () => ({}),
onEndReachedThreshold: 25
};

constructor(props) {
Expand All @@ -60,7 +65,8 @@ export default class Masonry extends Component {
initialOrientation: true,
_sortedData: [],
_resolvedData: [],
_columnHeights: columnHeights
_columnHeights: columnHeights,
_uniqueCount: props.bricks.length
};
// Assuming that rotation is binary (vertical|landscape)
Dimensions.addEventListener('change', (window) => this.setState(state => ({ initialOrientation: !state.initialOrientation })));
Expand All @@ -76,24 +82,28 @@ export default class Masonry extends Component {
// We use the difference in the passed in bricks to determine if user is appending or not
const brickDiff = differenceBy(nextProps.bricks, this.props.bricks, 'uri');
const appendedData = brickDiff.length !== nextProps.bricks.length;
const _uniqueCount = brickDiff.length + this.props.bricks.length;

// These intents would entail a complete re-render of the listview
if (differentColumns || differentPriority || !appendedData) {
this.setState(state => ({
_sortedData: [],
_resolvedData: [],
_columnHeights: generateColumnHeights(nextProps.columns)
_columnHeights: generateColumnHeights(nextProps.columns),
_uniqueCount
}), this.resolveBricks(nextProps));
}

// We use the existing data and only resolve what is needed
if (appendedData) {
this.resolveBricks({...nextProps, bricks: brickDiff});
return;
const offSet = this.props.bricks.length;
this.setState({
_uniqueCount
}, this.resolveBricks({...nextProps, bricks: brickDiff}, offSet));
}
}

resolveBricks({ bricks, columns }) {
resolveBricks({ bricks, columns }, offSet = 0) {
if (bricks.length === 0) {
// clear and re-render
this.setState(state => ({
Expand All @@ -105,7 +115,7 @@ export default class Masonry extends Component {
// Issues arrise if state changes occur in the midst of a resolve
bricks
.map((brick, index) => assignObjectColumn(columns, index, brick))
.map((brick, index) => assignObjectIndex(index, brick))
.map((brick, index) => assignObjectIndex(offSet + index, brick))
.map(brick => resolveImage(brick))
.map(resolveTask => resolveTask.fork(
(err) => console.warn('Image failed to load'),
Expand Down Expand Up @@ -174,29 +184,40 @@ export default class Masonry extends Component {
return dataCopy;
};

_delayCallEndReach = () => {
const sortedData = this.state._sortedData;
const sortedLength = sortedData.reduce((acc, cv) => cv.length + acc, 0);
// Limit the invokes to only when the masonry has
// fully loaded all of the content to ensure user fully reaches the end
if (sortedLength === this.state._uniqueCount) {
this.props.onEndReached();
}
}

render() {
return (
<View style={{flex: 1}} onLayout={(event) => this._setParentDimensions(event)}>
<ListView
contentContainerStyle={styles.masonry__container}
dataSource={this.state.dataSource}
enableEmptySections
scrollRenderAheadDistance={100}
removeClippedSubviews={false}
renderRow={(data, sectionId, rowID) => (
<Column
data={data}
columns={this.props.columns}
parentDimensions={this.state.dimensions}
imageContainerStyle={this.props.imageContainerStyle}
customImageComponent={this.props.customImageComponent}
customImageProps={this.props.customImageProps}
spacing={this.props.spacing}
key={`RN-MASONRY-COLUMN-${rowID}`} />
)}
<View style={{flex: 1}} onLayout={(event) => this._setParentDimensions(event)}>
<ListView
contentContainerStyle={styles.masonry__container}
dataSource={this.state.dataSource}
enableEmptySections
scrollRenderAheadDistance={100}
removeClippedSubviews={false}
onEndReached={this._delayCallEndReach}
onEndReachedThreshold={this.props.onEndReachedThreshold}
renderRow={(data, sectionId, rowID) => (
<Column
data={data}
columns={this.props.columns}
parentDimensions={this.state.dimensions}
imageContainerStyle={this.props.imageContainerStyle}
customImageComponent={this.props.customImageComponent}
customImageProps={this.props.customImageProps}
spacing={this.props.spacing}
key={`RN-MASONRY-COLUMN-${rowID}`} />
)}
refreshControl={this.props.refreshControl} />
</View>
</View>
);
}
};
54 changes: 36 additions & 18 deletions example/App/Container/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,17 +107,27 @@ let data = [
}
];

const addData = [
{
uri: 'https://i.pinimg.com/736x/48/ee/51/48ee519a1768245ce273363f5bf05f30--kaylaitsines-dipping-sauces.jpg'
},
{
uri: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQGYfU5N8lsJepQyoAigiijX8bcdpahei_XqRWBzZLbxcsuqtiH'
},
{
uri: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSPL2GTXDuOzwuX5X7Mgwc3Vc9ZIhiMmZUhp3s1wg0oHPzSP7qC'
}
];
const createBrick = uri => ({ uri });

const data1 = [
'https://i.pinimg.com/736x/48/ee/51/48ee519a1768245ce273363f5bf05f30--kaylaitsines-dipping-sauces.jpg',
'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQGYfU5N8lsJepQyoAigiijX8bcdpahei_XqRWBzZLbxcsuqtiH',
'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSPL2GTXDuOzwuX5X7Mgwc3Vc9ZIhiMmZUhp3s1wg0oHPzSP7qC',
].map(createBrick);

const data2 = [
'https://i.pinimg.com/236x/ee/ea/61/eeea61b03a2de8216384ff1be9544b82--what-you-see-follow-me.jpg',
'https://i.pinimg.com/originals/a0/30/87/a03087e36c083a665cd3392d2d59cf0b.jpg',
'https://i.pinimg.com/736x/59/ae/8e/59ae8ee0e9e84aa234f80a345fa7f1c6--wedding-ceremony-wedding-menu.jpg'
].map(createBrick);

const data3 = [
'https://i.pinimg.com/736x/6a/53/0a/6a530a49f764ce51a9742162f46c1e05--soul-food-pinterest.jpg',
'https://i.pinimg.com/originals/a0/30/87/a03087e36c083a665cd3392d2d59cf0b.jpg',
'https://img.buzzfeed.com/buzzfeed-static/static/2018-02/19/3/asset/buzzfeed-prod-fastlane-03/sub-buzz-12799-1519030318-7.jpg'
].map(createBrick);

const appendData = [data1, data2, data3];

export default class example extends Component {
constructor() {
Expand All @@ -126,15 +136,23 @@ export default class example extends Component {
this.state = {
columns: 2,
padding: 5,
data
data,
dataIndex: 0
};
}

_addData = () => {
const appendedData = [...data, ...addData];
this.setState({
data: appendedData
});
if (this.state.dataIndex < 3) {
this.setState(state => {
const addData = appendData[this.state.dataIndex];
const appendedData = [...state.data, ...addData];

return {
data: appendedData,
dataIndex: state.dataIndex + 1
};
});
}
}

render() {
Expand Down Expand Up @@ -181,10 +199,10 @@ export default class example extends Component {

<View style={{flex: 1, flexGrow: 10, padding: this.state.padding}}>
<Masonry
sorted
bricks={this.state.data}
columns={this.state.columns}
priority='order'
onEndReached={this._addData}
priority='balance'
customImageComponent={FastImage} />
</View>
</View>
Expand Down

0 comments on commit 0fd1ba5

Please sign in to comment.