Skip to content

Commit 28410af

Browse files
committed
Added InfiniteLoader demo and refactored other demos to share data
1 parent f07effd commit 28410af

File tree

10 files changed

+133
-37
lines changed

10 files changed

+133
-37
lines changed

source/AutoSizer/AutoSizer.example.css

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,17 @@
33
min-height: 200px;
44
}
55

6-
.Empty {
7-
width: 100%;
8-
height: 100%;
6+
.VirtualScroll {
7+
border: 1px solid #e0e0e0;
8+
}
9+
10+
.row {
911
display: flex;
10-
justify-content: center;
12+
flex-direction: row;
1113
align-items: center;
12-
background-color: #fafafa;
13-
border: 1px solid #e0e0e0;
14+
padding: 0 25px;
15+
background-color: #fff;
16+
border-bottom: 1px solid #e0e0e0;
1417
}
1518

1619
.checkboxLabel {

source/AutoSizer/AutoSizer.example.js

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,33 @@
11
/** @flow */
2-
import React, { Component } from 'react'
2+
import Immutable from 'immutable'
3+
import React, { Component, PropTypes } from 'react'
34
import { ContentBox, ContentBoxHeader, ContentBoxParagraph } from '../demo/ContentBox'
45
import AutoSizer from './AutoSizer'
56
import VirtualScroll from '../VirtualScroll'
67
import styles from './AutoSizer.example.css'
78

89
export default class AutoSizerExample extends Component {
10+
static propTypes = {
11+
list: PropTypes.instanceOf(Immutable.List).isRequired
12+
}
13+
914
constructor (props) {
1015
super(props)
1116

1217
this.state = {
1318
hideDescription: false
1419
}
20+
21+
this._rowRenderer = this._rowRenderer.bind(this)
1522
}
1623

1724
render () {
1825
const { hideDescription } = this.state
26+
const { list } = this.props
1927

2028
return (
2129
<ContentBox
22-
{...this.props}
30+
{...this.props}
2331
style={{
2432
maxHeight: 600
2533
}}
@@ -53,19 +61,30 @@ export default class AutoSizerExample extends Component {
5361
<div className={styles.AutoSizerWrapper}>
5462
<AutoSizer className={styles.AutoSizer}>
5563
<VirtualScroll
64+
className={styles.VirtualScroll}
5665
height={0}
57-
noRowsRenderer={() => (
58-
<div className={styles.Empty}>
59-
Empty &nbsp; <code>VirtualScroll</code>
60-
</div>
61-
)}
62-
rowsCount={0}
63-
rowHeight={1}
64-
rowRenderer={() => {}}
66+
rowsCount={list.size}
67+
rowHeight={30}
68+
rowRenderer={this._rowRenderer}
6569
/>
6670
</AutoSizer>
6771
</div>
6872
</ContentBox>
6973
)
7074
}
75+
76+
_rowRenderer (index) {
77+
const { list } = this.props
78+
const row = list.get(index)
79+
80+
return (
81+
<div
82+
key={index}
83+
className={styles.row}
84+
style={{ height: 30 }}
85+
>
86+
{row.name}
87+
</div>
88+
)
89+
}
7190
}

source/FlexTable/FlexTable.example.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
/** @flow */
2-
import React, { Component } from 'react'
2+
import Immutable from 'immutable'
3+
import React, { Component, PropTypes } from 'react'
34
import { ContentBox, ContentBoxHeader, ContentBoxParagraph } from '../demo/ContentBox'
45
import { LabeledInput, InputRow } from '../demo/LabeledInput'
56
import FlexColumn from './FlexColumn'
67
import FlexTable, { SortDirection } from './FlexTable'
78
import styles from './FlexTable.example.css'
89

910
export default class TableExample extends Component {
11+
static propTypes = {
12+
list: PropTypes.instanceOf(Immutable.List).isRequired
13+
}
14+
1015
constructor (props, context) {
1116
super(props, context)
1217

source/InfiniteLoader/InfiniteLoader.example.css

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
.VirtualScroll {
22
width: 100%;
33
border: 1px solid #DDD;
4-
margin-top: 15px;
54
}
65

76
.row {
@@ -18,3 +17,13 @@
1817
height: 1em;
1918
background-color: #DDD;
2019
}
20+
21+
.button {
22+
background-color: #4db6ac;
23+
color: #fff;
24+
appearance: none;
25+
border: none;
26+
padding: .5em 1em;
27+
border-radius: .35em;
28+
font-size: 1em;
29+
}

source/InfiniteLoader/InfiniteLoader.example.js

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/** @flow */
2-
import React, { Component } from 'react'
2+
import React, { Component, PropTypes } from 'react'
33
import { ContentBox, ContentBoxHeader, ContentBoxParagraph } from '../demo/ContentBox'
4+
import Immutable from 'immutable'
45
import InfiniteLoader from './InfiniteLoader'
56
import VirtualScroll from '../VirtualScroll'
67
import styles from './InfiniteLoader.example.css'
@@ -9,6 +10,10 @@ const STATUS_LOADING = 1
910
const STATUS_LOADED = 2
1011

1112
export default class InfiniteLoaderExample extends Component {
13+
static propTypes = {
14+
list: PropTypes.instanceOf(Immutable.List).isRequired
15+
}
16+
1217
constructor (props) {
1318
super(props)
1419

@@ -17,6 +22,7 @@ export default class InfiniteLoaderExample extends Component {
1722
randomScrollToIndex: null
1823
}
1924

25+
this._clearData = this._clearData.bind(this)
2026
this._isRowLoaded = this._isRowLoaded.bind(this)
2127
this._loadMoreRows = this._loadMoreRows.bind(this)
2228
this._rowRenderer = this._rowRenderer.bind(this)
@@ -37,17 +43,28 @@ export default class InfiniteLoaderExample extends Component {
3743
/>
3844

3945
<ContentBoxParagraph>
40-
Coming soon...
46+
This component manages just-in-time data fetching to ensure that the all visible rows have been loaded.
47+
It also uses a threshold to determine how early to pre-fetch rows (before a user scrolls to them).
48+
</ContentBoxParagraph>
49+
50+
<ContentBoxParagraph>
51+
<button
52+
className={styles.button}
53+
onClick={this._clearData}
54+
>
55+
Flush Cached Data
56+
</button>
4157
</ContentBoxParagraph>
4258

4359
<InfiniteLoader
60+
ref='InfiniteLoader'
4461
isRowLoaded={this._isRowLoaded}
4562
loadMoreRows={this._loadMoreRows}
4663
rowsCount={list.size}
4764
>
4865
<VirtualScroll
4966
className={styles.VirtualScroll}
50-
height={300}
67+
height={200}
5168
rowsCount={list.size}
5269
rowHeight={30}
5370
rowRenderer={this._rowRenderer}
@@ -58,6 +75,12 @@ export default class InfiniteLoaderExample extends Component {
5875
)
5976
}
6077

78+
_clearData () {
79+
this.setState({
80+
loadedRowsMap: {}
81+
})
82+
}
83+
6184
_isRowLoaded (index) {
6285
const { loadedRowsMap } = this.state
6386
return !!loadedRowsMap[index]
@@ -74,23 +97,29 @@ export default class InfiniteLoaderExample extends Component {
7497
for (var i = startIndex; i <= stopIndex; i++) {
7598
loadedRowsMap[i] = STATUS_LOADED
7699
}
100+
101+
promiseResolver()
77102
}, 1000) // TODO Randomize time?
103+
104+
let promiseResolver
105+
106+
return new Promise(resolve => promiseResolver = resolve)
78107
}
79108

80109
_rowRenderer (index) {
81110
const { list } = this.props
82111
const { loadedRowsMap } = this.state
83112

113+
const row = list.get(index)
84114
let content
85115

86116
if (loadedRowsMap[index] === STATUS_LOADED) {
87-
const row = list.get(index)
88117
content = row.name
89118
} else {
90119
content = (
91120
<div
92121
className={styles.placeholder}
93-
style={{ width: 100 + Math.round(Math.random() * 100) }}
122+
style={{ width: row.size }}
94123
/>
95124
)
96125
}

source/InfiniteLoader/InfiniteLoader.js

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,15 @@ export default class InfiniteLoader extends Component {
3030
isRowLoaded: PropTypes.func,
3131
/**
3232
* Callback to be invoked when more rows must be loaded.
33-
* It should implement the following signature: ({ startIndex, stopIndex }): void
33+
* It should implement the following signature: ({ startIndex, stopIndex }): Promise
34+
* The returned Promise should be resolved once row data has finished loading.
35+
* It will be used to determine when to refresh the list with the newly-loaded data.
3436
* This callback may be called multiple times in reaction to a single scroll event.
3537
*/
3638
loadMoreRows: PropTypes.func.isRequired,
37-
/** Number of rows in list; can be arbitrary high number if actual number is unknown. */
39+
/**
40+
* Number of rows in list; can be arbitrary high number if actual number is unknown.
41+
*/
3842
rowsCount: PropTypes.number,
3943
/**
4044
* Threshold at which to pre-fetch data.
@@ -45,7 +49,7 @@ export default class InfiniteLoader extends Component {
4549
}
4650

4751
static defaultProps = {
48-
threshold: 10
52+
threshold: 15
4953
}
5054

5155
constructor (props) {
@@ -61,7 +65,8 @@ export default class InfiniteLoader extends Component {
6165
child = React.cloneElement(
6266
child,
6367
{
64-
onRowsRendered: this._onRowsRendered
68+
onRowsRendered: this._onRowsRendered,
69+
ref: 'VirtualScroll'
6570
}
6671
)
6772

@@ -71,13 +76,29 @@ export default class InfiniteLoader extends Component {
7176
_onRowsRendered ({ startIndex, stopIndex }) {
7277
const { isRowLoaded, loadMoreRows, rowsCount, threshold } = this.props
7378

79+
this._lastRenderedStartIndex = startIndex
80+
this._lastRenderedStopIndex = stopIndex
81+
7482
const unloadedRanges = scanForUnloadedRanges({
7583
isRowLoaded,
7684
startIndex: Math.max(0, startIndex - threshold),
7785
stopIndex: Math.min(rowsCount, stopIndex + threshold)
7886
})
7987

80-
unloadedRanges.forEach(unloadedRange => loadMoreRows(unloadedRange))
88+
unloadedRanges.forEach(unloadedRange => {
89+
let promise = loadMoreRows(unloadedRange)
90+
if (promise) {
91+
promise.then(() => {
92+
// Refresh the visible rows if any of them have just been loaded
93+
if (!(
94+
unloadedRange.startIndex > this._lastRenderedStopIndex ||
95+
unloadedRange.stopIndex < this._lastRenderedStartIndex
96+
)) {
97+
this.refs.VirtualScroll.forceUpdate()
98+
}
99+
})
100+
}
101+
})
81102
}
82103
}
83104

source/InfiniteLoader/InfiniteLoader.test.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ describe('InfiniteLoader', () => {
6969
renderOrUpdateComponent()
7070
expect(loadMoreRowsCalls).toEqual([{ startIndex: 0, stopIndex: 14 }])
7171
})
72+
73+
// TODO Check to ensure that forceUpdate is called if and only if rows are visible
7274
})
7375

7476
describe('scanForUnloadedRanges', () => {

source/VirtualScroll/VirtualScroll.example.js

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
/**
22
* @flow
33
*/
4-
import React, { Component } from 'react'
4+
import Immutable from 'immutable'
5+
import React, { Component, PropTypes } from 'react'
6+
import styles from './VirtualScroll.example.css'
7+
import VirtualScroll from './VirtualScroll'
58
import { ContentBox, ContentBoxHeader, ContentBoxParagraph } from '../demo/ContentBox'
69
import { LabeledInput, InputRow } from '../demo/LabeledInput'
7-
import VirtualScroll from './VirtualScroll'
8-
import styles from './VirtualScroll.example.css'
910

1011
export default class VirtualScrollExample extends Component {
12+
static propTypes = {
13+
list: PropTypes.instanceOf(Immutable.List).isRequired
14+
}
15+
1116
constructor (props) {
1217
super(props)
1318

@@ -143,13 +148,13 @@ export default class VirtualScrollExample extends Component {
143148

144149
const datum = list.get(index)
145150
const height = useDynamicRowHeight
146-
? datum.height
151+
? datum.size
147152
: virtualScrollRowHeight
148153

149154
let additionalContent
150155

151156
if (useDynamicRowHeight) {
152-
switch (datum.height) {
157+
switch (datum.size) {
153158
case 75:
154159
additionalContent = <div>It is medium-sized.</div>
155160
break
@@ -184,7 +189,7 @@ export default class VirtualScrollExample extends Component {
184189
</div>
185190
{useDynamicRowHeight &&
186191
<span className={styles.height}>
187-
{datum.height}px
192+
{datum.size}px
188193
</span>
189194
}
190195
</div>

source/demo/Application.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,10 @@ render((
5757
className={styles.column}
5858
list={list}
5959
/>
60-
<AutoSizerExample className={styles.column}/>
60+
<AutoSizerExample
61+
className={styles.column}
62+
list={list}
63+
/>
6164
<InfiniteLoaderExample
6265
className={styles.column}
6366
list={list}

source/demo/utils.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ export function generateRandomList () {
77
for (var i = 0; i < 1000; i++) {
88
list.push({
99
color: BADGE_COLORS[i % BADGE_COLORS.length],
10-
height: ROW_HEIGHTS[Math.floor(Math.random() * ROW_HEIGHTS.length)],
1110
index: i,
1211
name: NAMES[i % NAMES.length],
13-
random: loremIpsum[i % loremIpsum.length]
12+
random: loremIpsum[i % loremIpsum.length],
13+
size: ROW_HEIGHTS[Math.floor(Math.random() * ROW_HEIGHTS.length)]
1414
})
1515
}
1616

0 commit comments

Comments
 (0)