<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/react/dist/react-with-addons.min.js"></script>
<script src="https://unpkg.com/react-dom/dist/react-dom.min.js"></script>
<script src="https://unpkg.com/react-virtualized/dist/umd/react-virtualized.js"></script>
<link rel="stylesheet" href="https://unpkg.com/react-virtualized/styles.css">
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div id="root"></div>
<script src="script.js"></script>
</body>
</html>
var InfiniteLoader = ReactVirtualized.InfiniteLoader;
var WindowScroller = ReactVirtualized.WindowScroller;
var AutoSizer = ReactVirtualized.AutoSizer;
var Table = ReactVirtualized.Table;
var Column = ReactVirtualized.Column;
var Component = React.Component;
function getServerData(search, fromIdx) {
let data = [];
if (search === 'banana') {
// Server has absolutely no bananas :(
} else if (search === 'carrots') {
// Server has only 1 carrot
for (let i = 0; i < 1; i++) {
data.push({name: `carrots ${i}`});
}
} else if (search === 'tomatoes') {
// Server has only 5 tomatoes
for (let i = 0; i < 5; i++) {
data.push({name: `tomatoes ${i}`});
}
} else if (search === 'birds') {
// Server has exactly 200 birds
if (fromIdx === 200)
return data;
for (let i = fromIdx; i < fromIdx+100; i++) {
data.push({name: `${search} ${i}`});
}
} else {
// Server has tones of potatoes or other stuff
for (let i = fromIdx; i < fromIdx+100; i++) {
data.push({name: `${search} ${i}`});
}
}
return data;
}
class App extends Component {
constructor(props) {
super(props);
this.state = {
list: [],
rowCount: 0,
search: 'potatoes',
};
}
loadMoreRows({ startIndex, stopIndex }) {
console.log('LoadMoreRows', this.state.search);
return new Promise((resolve, reject) => {
setTimeout(() => {
const newDataFromServer = getServerData(this.state.search, this.state.list.length);
const newList = this.state.list.concat(newDataFromServer);
const hasNextPage = newDataFromServer.length === 100;
const newCount = hasNextPage ? newList.length + 1 : newList.length;
this.setState({
rowCount: newCount,
list: newList,
}, () => {
resolve();
});
}, 1000);
});
}
isRowLoaded({ index }) {
//console.log(`isRowLoaded (${index < this.state.list.length}) index: ${index}, listLength: ${this.state.list.length}`);
return index < this.state.list.length;
}
rowGetter({index}) {
if (!this.isRowLoaded({ index })) {
return {name: 'loading...'};
}
return this.state.list[index];
}
// Empty the list and set row count to 1 to enable infinite scroll
// I think we should reset memoizer here.
resetList() {
this.setState({list: [], rowCount: 1});
}
componentDidMount() {
// When we first start, we want to call the server for data
this.resetList();
}
searchChanged(evt) {
this.setState({search: evt.target.value});
}
handleSearch(evt) {
// We want to call the server again
this.resetList();
}
render() {
return (
<div>
<input type="text" value={this.state.search} onChange={this.searchChanged.bind(this)} />
<button onClick={this.handleSearch.bind(this)}>Search</button>
<InfiniteLoader isRowLoaded={this.isRowLoaded.bind(this)}
loadMoreRows={this.loadMoreRows.bind(this)}
rowCount={this.state.rowCount}
threshold={10}>
{({ onRowsRendered, registerChild }) => (
<WindowScroller>
{({ height, isScrolling, scrollTop }) => (
<AutoSizer disableHeight>
{({ width }) => (
<Table ref={registerChild}
autoHeight
height={height}
width={width}
headerHeight={27}
onRowsRendered={onRowsRendered}
rowCount={this.state.rowCount}
rowHeight={27}
scrollTop={scrollTop}
noRowsRenderer={() => <div style={{textAlign: 'center'}}>No rows</div>}
rowGetter={this.rowGetter.bind(this)}
>
<Column
label='Name'
dataKey='name'
width={100}
maxWidth={400}
/>
</Table>
)}
</AutoSizer>
)}
</WindowScroller>
)}
</InfiniteLoader>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
.List {
border: 1px solid black;
box-sizing: border-box;
}
.Row {
width: 100%;
height: 100%;
border-bottom: 1px solid black;
display: flex;
align-items: center;
padding: 0 0.5rem;
box-sizing: border-box;
}