├── .babelrc
├── .editorconfig
├── .gitignore
├── CONTRIBUTING.md
├── README.md
├── dist
└── index.js
├── example
├── config
│ ├── loaders.js
│ ├── pages.js
│ ├── plugins.js
│ ├── webpack.config.dev.js
│ └── webpack.config.prod.js
├── server
│ ├── index.js
│ └── middleware
│ │ ├── single-page-middleware.js
│ │ └── webpack-middleware.js
└── src
│ ├── index.handlebars
│ ├── js
│ ├── App.js
│ └── InfiniteScrollExample.js
│ ├── main.js
│ ├── routes.js
│ └── styles
│ ├── 3rdparty
│ └── _bootstrap.scss
│ └── screen.scss
├── index.js
└── package.json
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["es2015", { "modules": false, "loose" : true }],
4 | "react",
5 | "stage-2"
6 | ],
7 | "plugins": ["react-hot-loader/babel"]
8 | }
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain
2 | # consistent coding styles between different editors and IDEs.
3 |
4 | root = true
5 |
6 | [*.js]
7 | charset = utf-8
8 | indent_style = space
9 | indent_size = 2
10 | end_of_line = lf
11 | insert_final_newline = true
12 | trim_trailing_whitespace = true
13 |
14 | [*.md]
15 | insert_final_newline = false
16 | trim_trailing_whitespace = false
17 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .idea
3 | *.iml
4 | node_modules/
5 | *.log*
6 | npm-debug.log
7 | mobile/common/
8 | *.pyc
9 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 | We're always looking to improve this project, open source contribution is encouraged so long as they adhere to our guidelines.
3 |
4 | # Pull Requests
5 |
6 | The Solid State team will be monitoring for pull requests. When we get one, a member of team will test the work against our internal uses and sign off on the changes. From here, we'll either merge the pull request or provide feedback suggesting the next steps.
7 |
8 | **A couple things to keep in mind:**
9 |
10 | - If you've changed APIs, update the documentation.
11 | - Keep the code style (indents, wrapping) consistent.
12 | - If your PR involves a lot of commits, squash them using ```git rebase -i``` as this makes it easier for us to review.
13 | - Keep lines under 80 characters.
14 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React Virtualized Infinite Scroll Component
2 |
3 | A React component that provides you with an infinite scrolling list that be used in either direction.
4 |
5 | ## Install
6 | ```
7 | $ npm install react-virtualized-infinite-scroll --save
8 | ```
9 |
10 | ## Usage
11 |
12 | ```
13 | import InfiniteScroll from 'react-virtualized-infinite-scroll';
14 |
15 | ...
16 |
17 | loadMore = () => {
18 | // Set state to loading data
19 | this.setState({
20 | isLoading: true,
21 | });
22 |
23 | // @TODO Perform asychronous load of data
24 | }
25 |
26 | // Optional function to return dynamic row height
27 | rowHeight = ({ index }) => {
28 | return index < this.state.data.length ? this.state.data[index].height : 40;
29 | }
30 |
31 | // Example function for adding data to the bottom of the list in reverse mode
32 | addToBottom = () => {
33 | let data = this.state.data.slice(0);
34 | data.unshift({ key: data.length, height: this.getRandomHeight() });
35 | this.setState({ data });
36 | if (this.infiniteScroll) {
37 | this.infiniteScroll.adjustScrollPos(this.state.randomRowHeights ? data.height * -1 : -40);
38 | }
39 | }
40 |
41 | renderRow (row) {
42 | return (
43 |
44 | Row {row.key + 1}
45 |
46 | );
47 | }
48 |
49 | render () {
50 | return (
51 |
52 |
61 | Loading...
62 |
63 | )}
64 | containerHeight={200}
65 | ref={(infiniteScroll) => this.infiniteScroll = infiniteScroll}
66 | scrollRef={(virtualScroll) => this.virtualScroll = virtualScroll}
67 | reverse={this.props.reverse}
68 | />
69 |
70 | );
71 | }
72 |
73 | ```
74 |
75 | ### Prop Types
76 | | Property | Type | Required? | Description |
77 | |:---|:---|:---:|:---|
78 | | loadMore | Function | ✓ | Callback used for loading more data |
79 | | renderRow | Function | ✓ | Used to render each row |
80 | | rowHeight | Number or Function | ✓ | Either a fixed row height (number) or a function that returns the height of a row given its index: `({ index: number }): number` |
81 | | threshold | Number | ✓ | How many rows before the bottom (or top in reverse mode) to request more data |
82 | | isLoading | Bool | | While true a loading item is shown at the bottom (or top in reverse mode). Useful while loading more data |
83 | | scrollToRow | Number | | Row index to ensure visible (by forcefully scrolling if necessary) |
84 | | renderLoading | Object | | Render a custom loading item |
85 | | data | Array | | Data array |
86 | | containerHeight | Number | | Force a height on the entire list component. Default is to auto fill available space |
87 | | reverse | Bool | | Reverse scroll direction. Defaults to `false` |
88 | | scrollRef | Function | | Callback used to give back reference to underlying virtual scroll component for finer control |
89 |
90 | ## Development
91 | Should you wish to develop this module further start by cloning this repository
92 |
93 | ### Run Dev - Run hot reloading node server
94 | ```
95 | $ npm start
96 | ```
97 |
98 | ### Run Prod - Build, deploy, minify npm module
99 | ```
100 | $ npm run prod
101 | ```
102 |
103 | ### Testing the module
104 | See ```InfiniteScrollExample.js```, this component imports your developed module, if you wish to point to production then uncomment the other import line for InfiniteScroll
105 |
106 | # Getting Help
107 | If you encounter a bug or feature request we would like to hear about it. Before you submit an issue please search existing issues in order to prevent duplicates.
108 |
109 | # Contributing
110 | For more information about contributing PRs, please see our Contribution Guidelines.
111 |
112 |
113 | # Get in touch
114 | If you have any questions about our projects you can email projects@solidstategroup.com.
115 |
--------------------------------------------------------------------------------
/example/config/loaders.js:
--------------------------------------------------------------------------------
1 | module.exports = [
2 | {
3 | test: /\.css$/,
4 | loaders: ['style', 'css']
5 | },
6 | {
7 | test: /\.js?/,
8 | exclude: /node_modules/,
9 | loaders: ['babel']
10 | },
11 | {
12 | test: /\.html$/,
13 | loader: 'html-loader?attrs[]=source:src&attrs[]=img:src'
14 | },
15 | {
16 | test: /\.(jpe?g|png|gif|svg|mp4|webm)$/i,
17 | loaders: [
18 | 'file?hash=sha512&digest=hex&name=[hash].[ext]',
19 | 'image-webpack'
20 | ]
21 | }
22 | ];
--------------------------------------------------------------------------------
/example/config/pages.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by kylejohnson on 14/09/2016.
3 | */
4 | module.exports = ['index'];
--------------------------------------------------------------------------------
/example/config/plugins.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const pages = require('./pages');
3 | const CopyWebpackPlugin = require('copy-webpack-plugin');
4 |
5 | module.exports = [
6 | //Copy static content
7 | new CopyWebpackPlugin([
8 | { from: './src/images', to: './build/images' },
9 | { from: './src/fonts', to: path.join(__dirname, '../build/fonts') }
10 | ], { copyUnmodified: true })
11 | ];
--------------------------------------------------------------------------------
/example/config/webpack.config.dev.js:
--------------------------------------------------------------------------------
1 | // webpack.config.dev.js
2 | var path = require('path')
3 | var src = path.join(__dirname, '../src') + '/';
4 | var webpack = require('webpack');
5 |
6 | module.exports = {
7 | devtool: 'eval',
8 | entry: [
9 | 'webpack-hot-middleware/client',
10 | 'react-hot-loader/patch',
11 | './example/src/main.js',
12 | ],
13 | output: {
14 | path: '/',
15 | publicPath: 'http://localhost:3000/example/build/',
16 | filename: '[name].js'
17 | },
18 | plugins: [
19 | new webpack.HotModuleReplacementPlugin(),
20 | new webpack.NoErrorsPlugin()
21 | ],
22 | module: {
23 | loaders: require('./loaders')
24 | .concat([
25 | {
26 | test: /\.scss$/,
27 | loaders: ['style', 'css', 'sass']
28 | }
29 | ]),
30 | }
31 | };
--------------------------------------------------------------------------------
/example/config/webpack.config.prod.js:
--------------------------------------------------------------------------------
1 | // webpack.config.prod.js
2 | // Watches + deploys files minified + cachebusted
3 | var path = require('path');
4 | var webpack = require('webpack');
5 | const CleanWebpackPlugin = require('clean-webpack-plugin');
6 | var libName = 'index';
7 | var outputFile = libName + '.js';
8 |
9 | module.exports = {
10 | devtool: 'source-map',
11 |
12 | entry: [
13 | './index.js',
14 | ],
15 |
16 | module: {
17 | loaders: require('./loaders')
18 | },
19 |
20 | output: {
21 | publicPath: '/',
22 | path: './dist',
23 | filename: outputFile,
24 | library: libName,
25 | libraryTarget: 'umd',
26 | umdNamedDefine: true
27 | },
28 |
29 | plugins: [
30 | //Clear out build folder
31 | new CleanWebpackPlugin(['dist'], { root: '../' }),
32 |
33 | //Ensure NODE_ENV is set to production
34 | new webpack.DefinePlugin({
35 | 'process.env': {
36 | 'NODE_ENV': JSON.stringify('production')
37 | },
38 | __DEV__: JSON.stringify(JSON.parse(process.env.DEBUG || 'false'))
39 | }),
40 |
41 | //remove duplicate files
42 | new webpack.optimize.DedupePlugin(),
43 |
44 | new webpack.LoaderOptionsPlugin({
45 | minimize: true,
46 | debug: false
47 | }),
48 |
49 | //Uglify
50 | new webpack.optimize.UglifyJsPlugin({
51 | compress: {
52 | warnings: false,
53 | 'screw_ie8': true
54 | },
55 | output: {
56 | comments: false
57 | },
58 | sourceMap: false
59 | }),
60 |
61 | ]
62 | }
63 | ;
--------------------------------------------------------------------------------
/example/server/index.js:
--------------------------------------------------------------------------------
1 | import express from 'express';
2 | import exphbs from 'express-handlebars';
3 | import spm from './middleware/single-page-middleware';
4 | import webpackMiddleware from './middleware/webpack-middleware';
5 | const isDev = process.env.NODE_ENV !== 'production';
6 | const app = express();
7 |
8 | console.log('Enabled Webpack Hot Reloading');
9 | webpackMiddleware(app);
10 |
11 | app.set('views', 'example/src/');
12 | app.use(express.static('example/src'));
13 |
14 | app.use(spm);
15 | app.engine('handlebars', exphbs());
16 | app.set('view engine', 'handlebars');
17 |
18 | app.get('/', function (req, res) {
19 | res.render('index', {
20 | isDev,
21 | layout: false
22 | });
23 | });
24 |
25 | app.listen(3000, function () {
26 | console.log('express-handlebars example server listening on: 3000');
27 | });
--------------------------------------------------------------------------------
/example/server/middleware/single-page-middleware.js:
--------------------------------------------------------------------------------
1 | module.exports = function(req, res, next) {
2 |
3 | const headers = req.headers;
4 | let rewriteTarget = '/index.html';
5 |
6 | if (req.method !== 'GET') {
7 | console.log(
8 | 'Not rewriting',
9 | req.method,
10 | req.url,
11 | 'because the method is not GET.'
12 | );
13 | req.url = rewriteTarget.split('/')[rewriteTarget.split('/').length-1];
14 | return next();
15 | } else if (!headers || typeof headers.accept !== 'string') {
16 | console.log(
17 | 'Not rewriting',
18 | req.method,
19 | req.url,
20 | 'because the client did not send an HTTP accept header.'
21 | );
22 | req.url = rewriteTarget.split('/')[rewriteTarget.split('/').length-1];
23 | return next();
24 | } else if (headers.accept.indexOf('application/json') === 0) {
25 | console.log(
26 | 'Not rewriting',
27 | req.method,
28 | req.url,
29 | 'because the client prefers JSON.'
30 | );
31 | req.url = rewriteTarget.split('/')[rewriteTarget.split('/').length-1];
32 | return next();
33 | } else if (headers.accept.indexOf('html') == -1) {
34 | console.log(
35 | 'Not rewriting',
36 | req.method,
37 | req.url,
38 | 'because the client does not accept HTML.'
39 | );
40 | req.url = '/' + req.url.split('/')[req.url.split('/').length-1];
41 | return next();
42 | };
43 |
44 | var parsedUrl = req.url;
45 |
46 | if (parsedUrl.indexOf('.') !== -1 && parsedUrl.indexOf('t/') == -1) {
47 | console.log(
48 | 'Not rewriting',
49 | req.method,
50 | req.url,
51 | 'because the path includes a dot (.) character.'
52 | );
53 | return next();
54 | }
55 |
56 | rewriteTarget = '/';
57 | req.url = rewriteTarget;
58 | next();
59 | };
--------------------------------------------------------------------------------
/example/server/middleware/webpack-middleware.js:
--------------------------------------------------------------------------------
1 | //Uses webpack dev + hot middleware
2 | import webpack from 'webpack';
3 | import config from '../../config/webpack.config.dev';
4 | import webpackDevMiddleware from 'webpack-dev-middleware';
5 | import webpackHotMiddleware from 'webpack-hot-middleware';
6 |
7 | const compiler = webpack(config);
8 |
9 | module.exports = function (app) {
10 | const middleware = webpackDevMiddleware(compiler, {
11 | publicPath: config.output.publicPath,
12 | contentBase: 'example/src',
13 | stats: { colors: true },
14 | });
15 | app.use(middleware);
16 |
17 | app.use(webpackHotMiddleware(compiler, {
18 | log: console.log,
19 | path: '/__webpack_hmr',
20 | heartbeat: 10 * 1000
21 | }));
22 | return middleware;
23 | };
--------------------------------------------------------------------------------
/example/src/index.handlebars:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | React Virtualized Infinite Scroll
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/example/src/js/App.js:
--------------------------------------------------------------------------------
1 | import React, {Component, PropTypes} from 'react';
2 | import DocumentTitle from 'react-document-title';
3 | import InfiniteScrollExample from './InfiniteScrollExample';
4 |
5 | const App = class extends Component {
6 | displayName: 'App';
7 |
8 | render () {
9 | return (
10 |
11 |
12 | Async Infinite Scroll
13 |
14 |
15 |
16 | Async Infinite Reverse Scroll
17 |
18 |
19 |
20 | );
21 | }
22 | };
23 |
24 | App.propTypes = {};
25 |
26 | module.exports = App;
--------------------------------------------------------------------------------
/example/src/js/InfiniteScrollExample.js:
--------------------------------------------------------------------------------
1 | import React, {Component, PropTypes} from 'react';
2 | import ReactDOM from 'react-dom';
3 | import InfiniteScroll from '../../../';
4 | const range = require('lodash.range');
5 | //import InfiniteScroll from '../../../dist/';
6 |
7 | class InfiniteScrollExample extends Component {
8 | constructor (props) {
9 | super(props);
10 | this.state = {
11 | data: range(0, 100).map((num) => ({ key: num, height: this.getRandomHeight() })),
12 | randomRowHeights: false,
13 | };
14 | }
15 |
16 | getRandomHeight () {
17 | return Math.round(Math.random() * 60 + 40);
18 | }
19 |
20 | loadMore = () => {
21 | this.setState({
22 | isLoading: true,
23 | });
24 | setTimeout(() => {
25 | const length = this.state.data.length;
26 | this.setState({
27 | isLoading: false,
28 | data: this.state.data.concat(range(length, length + 100)
29 | .map((num) => ({ key: num, height: this.getRandomHeight() })))
30 | });
31 | }, 2000);
32 | }
33 |
34 | addToBottom = () => {
35 | let data = this.state.data.slice(0);
36 | data.unshift({ key: data.length, height: this.getRandomHeight() });
37 | this.setState({ data });
38 | if (this.infiniteScroll) {
39 | this.infiniteScroll.adjustScrollPos(this.state.randomRowHeights ? data.height * -1 : -40);
40 | }
41 | }
42 |
43 | rowHeight = ({ index }) => {
44 | if (!this.state.randomRowHeights) {
45 | return 40;
46 | }
47 | return index < this.state.data.length ? this.state.data[index].height : 40;
48 | }
49 |
50 | onRandomRowHeightsChanged = (randomRowHeights) => {
51 | this.setState({ randomRowHeights });
52 | this.virtualScroll.recomputeRowHeights();
53 | }
54 |
55 | renderRow (data) {
56 | return (
57 |
58 | Row {data.key + 1}
59 |
60 | );
61 | }
62 |
63 | render () {
64 | return (
65 |
66 |
69 | Loading...
70 |
71 | )}
72 | rowHeight={this.rowHeight}
73 | containerHeight={200}
74 | threshold={50}
75 | data={this.state.data}
76 | isLoading={this.state.isLoading}
77 | loadMore={this.loadMore}
78 | renderRow={this.renderRow}
79 | ref={(infiniteScroll) => this.infiniteScroll = infiniteScroll}
80 | scrollRef={(virtualScroll) => this.virtualScroll = virtualScroll}
81 | reverse={this.props.reverse}
82 | />
83 |
84 | Use random row heights ?
85 |
86 |
87 | {this.props.reverse && }
88 |
89 | );
90 | }
91 | };
92 |
93 | InfiniteScrollExample.propTypes = {
94 | reverse: PropTypes.bool
95 | }
96 |
97 | InfiniteScrollExample.defaultProps = {
98 | reverse: false
99 | }
100 |
101 | export default InfiniteScrollExample;
102 |
--------------------------------------------------------------------------------
/example/src/main.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by kylejohnson on 16/09/2016.
3 | */
4 | import './styles/screen.scss';
5 | import {render} from 'react-dom';
6 | import {Router, browserHistory} from 'react-router';
7 | import routes from './routes';
8 | import React from 'react';
9 | import {AppContainer} from 'react-hot-loader';
10 |
11 | // Render the React application to the DOM
12 |
13 | const el = ;
14 | const renderEl = () => {
15 | render(
16 |
17 | {el}
18 | ,
19 | document.getElementById('react')
20 | );
21 | };
22 |
23 | renderEl();
24 | if (module.hot) {
25 | module.hot.accept('./routes', renderEl);
26 | }
--------------------------------------------------------------------------------
/example/src/routes.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {render} from 'react-dom'
3 | import { Route, IndexRoute } from 'react-router'
4 | import App from './js/App';
5 |
6 |
7 | module.exports = (
8 |
9 | );
--------------------------------------------------------------------------------
/example/src/styles/3rdparty/_bootstrap.scss:
--------------------------------------------------------------------------------
1 | $enable-flex: true;
2 |
3 | // Core variables and mixins
4 | //@import "~bootstrap/scss/custom";
5 | @import "../../../node_modules/bootstrap/scss/variables";
6 | @import "../../../node_modules/bootstrap/scss/mixins";
7 |
8 | // Reset and dependencies
9 | @import "../../../node_modules/bootstrap/scss/normalize";
10 | @import "../../../node_modules/bootstrap/scss/print";
11 |
12 | // Core CSS
13 | @import "../../../node_modules/bootstrap/scss/reboot";
14 | //@import "~bootstrap/scss/type";
15 | //@import "~bootstrap/scss/images";
16 | //@import "~bootstrap/scss/code";
17 | @import "../../../node_modules/bootstrap/scss/tables";
18 | @import "../../../node_modules/bootstrap/scss/grid";
19 | //@import "~bootstrap/scss/forms";
20 | @import "../../../node_modules/bootstrap/scss/buttons";
21 |
22 | // Components
23 | @import "../../../node_modules/bootstrap/scss/animation";
24 | //@import "~bootstrap/scss/dropdown";
25 | //@import "~bootstrap/scss/button-group";
26 | //@import "~bootstrap/scss/input-group";
27 | //@import "~bootstrap/scss/custom-forms";
28 | @import "../../../node_modules/bootstrap/scss/nav";
29 | @import "../../../node_modules/bootstrap/scss/navbar";
30 | //@import "~bootstrap/scss/card";
31 | //@import "~bootstrap/scss/breadcrumb";
32 | //@import "~bootstrap/scss/pagination";
33 | //@import "~bootstrap/scss/tags";
34 | //@import "~bootstrap/scss/jumbotron";
35 | //@import "~bootstrap/scss/alert";
36 | //@import "~bootstrap/scss/progress";
37 | //@import "~bootstrap/scss/media";
38 | //@import "~bootstrap/scss/list-group";
39 | @import "../../../node_modules/bootstrap/scss/responsive-embed";
40 | //@import "~bootstrap/scss/close";
41 |
42 | // Components w/ JavaScript
43 | @import "../../../node_modules/bootstrap/scss/modal";
44 | //@import "~bootstrap/scss/tooltip";
45 | //@import "popover";
46 | //@import "~bootstrap/scss/carousel";
47 |
48 | // Utility classes
49 | @import "../../../node_modules/bootstrap/scss/utilities";
50 |
--------------------------------------------------------------------------------
/example/src/styles/screen.scss:
--------------------------------------------------------------------------------
1 | @import "~compass-mixins";
2 | // 3rd party
3 | @import "3rdparty/bootstrap";
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 | import ReactDOM from 'react-dom';
3 | import 'react-virtualized/styles.css';
4 | import { AutoSizer, InfiniteLoader, VirtualScroll } from 'react-virtualized';
5 |
6 | class InfiniteScroll extends Component {
7 |
8 | constructor(props) {
9 | super(props);
10 | this.state = {
11 | scrollToBottom: true
12 | };
13 | this.scrollHeight = 0;
14 | }
15 |
16 | loadMore = () => {
17 | if (!this.props.isLoading) {
18 | this.props.loadMore();
19 | }
20 | }
21 |
22 | isRowLoaded = ({ index }) => (
23 | (this.props.reverse ? this.props.data.length - 1 - index : index) < this.props.data.length - this.props.threshold
24 | )
25 |
26 | onScroll = ({ clientHeight, scrollHeight, scrollTop }) => {
27 | if (!this.props.reverse) {
28 | return;
29 | }
30 |
31 | // Keep track of scroll height
32 | this.scrollHeight = scrollHeight;
33 |
34 | // Check whether initial scroll to bottom in reverse mode has completed
35 | if (this.state.scrollToBottom && scrollTop === scrollHeight - clientHeight) {
36 | this.setState({ scrollToBottom: false });
37 | }
38 | }
39 |
40 | adjustScrollPos = (adj) => {
41 | const virtualScroll = ReactDOM.findDOMNode(this.virtualScroll);
42 | if (virtualScroll.scrollTop !== virtualScroll.scrollHeight - virtualScroll.clientHeight) {
43 | virtualScroll.scrollTop += adj;
44 | }
45 | }
46 |
47 | componentWillReceiveProps(newProps) {
48 | // Scroll to bottom on initial data in reverse mode only
49 | if (this.props.reverse &&
50 | (!this.props.data || this.props.data.length === 0) &&
51 | newProps.data && newProps.data.length) {
52 | this.setState({ scrollToBottom: true });
53 | }
54 | }
55 |
56 | componentDidUpdate(prevProps, prevState) {
57 | if (!this.props.reverse) {
58 | return;
59 | }
60 |
61 | // Re-measure all estimated rows if data has changed or loading is displayed
62 | if (prevProps.data && this.props.data &&
63 | (prevProps.data.length !== this.props.data.length ||
64 | prevProps.isLoading !== this.props.isLoading)) {
65 | this.virtualScroll.measureAllRows();
66 | }
67 |
68 | // Get total size directly from the grid (which is updated by measureAllRows())
69 | const totalSize = this.virtualScroll.Grid._rowSizeAndPositionManager.getTotalSize();
70 |
71 | // Get the DOM node for the virtual scroll
72 | const virtualScroll = ReactDOM.findDOMNode(this.virtualScroll);
73 |
74 | // With a valid scroll height and as long as we do not need to scroll to bottom
75 | if (virtualScroll && totalSize && !prevState.scrollToBottom) {
76 | // If the scroll height has changed, adjust the scroll position accordingly
77 | if (this.scrollHeight !== totalSize) {
78 | virtualScroll.scrollTop += totalSize - this.scrollHeight;
79 | this.scrollHeight = totalSize;
80 | }
81 | }
82 | }
83 |
84 | rowRenderer = ({ index }) => {
85 | if (this.props.reverse) {
86 | // Data needs to be rendered in reverse order, check for loading
87 | if (this.props.isLoading) {
88 | // Data is shifted down by 1 while loading
89 | if (index >= 1 && index < this.props.data.length + 1) {
90 | return this.props.renderRow(this.props.data[this.props.data.length - index]);
91 | }
92 |
93 | return this.props.renderLoading;
94 | }
95 |
96 | return this.props.renderRow(this.props.data[this.props.data.length - 1 - index]);
97 | }
98 |
99 | if (index < this.props.data.length) {
100 | return this.props.renderRow(this.props.data[index]);
101 | }
102 |
103 | return this.props.renderLoading;
104 | }
105 |
106 | render () {
107 | const { isLoading, data, containerHeight, rowHeight, scrollToRow, reverse, scrollRef } = this.props;
108 | const rowCount = isLoading ? data.length + 1 : data.length;
109 |
110 | return (
111 |
112 | {({ height, width }) => (
113 |
118 | {({ onRowsRendered, registerChild }) => (
119 | {
127 | this.virtualScroll = virtualScroll;
128 | scrollRef && scrollRef(virtualScroll);
129 | registerChild(virtualScroll);
130 | }}
131 | onRowsRendered={onRowsRendered}
132 | rowRenderer={this.rowRenderer}
133 | onScroll={this.onScroll}
134 | />
135 | )}
136 |
137 | )}
138 |
139 | );
140 | }
141 | };
142 |
143 | InfiniteScroll.propTypes = {
144 | loadMore: PropTypes.func.isRequired,
145 | renderRow: PropTypes.func.isRequired,
146 | rowHeight: PropTypes.oneOfType([PropTypes.number, PropTypes.func]).isRequired,
147 | threshold: PropTypes.number.isRequired,
148 | isLoading: PropTypes.bool,
149 | scrollToRow: PropTypes.number,
150 | renderLoading: PropTypes.object,
151 | data: PropTypes.array,
152 | containerHeight: PropTypes.number,
153 | reverse: PropTypes.bool,
154 | scrollRef: PropTypes.func,
155 | };
156 |
157 | InfiniteScroll.defaultProps = {
158 | isLoading: false,
159 | renderLoading: (
160 |
161 | Loading...
162 |
163 | ),
164 | reverse: false
165 | }
166 |
167 | module.exports = InfiniteScroll;
168 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-virtualized-infinite-scroll",
3 | "version": "0.0.4",
4 | "description": "Infinite scroll list for React that also works in reverse",
5 | "license": "MIT",
6 | "author": "kyle-ssg",
7 | "scripts": {
8 | "prod": "webpack --config ./example/config/webpack.config.prod.js",
9 | "server": "babel-node example/server --presets=es2015-node5,stage-2,react",
10 | "start": "nodemon --watch example/server --watch example/config --exec npm run server"
11 | },
12 | "devDependencies": {
13 | "babel-cli": "6.14.0",
14 | "babel-core": "6.14.0",
15 | "babel-eslint": "6.1.2",
16 | "babel-loader": "6.2.5",
17 | "babel-preset-es2015": "6.14.0",
18 | "babel-preset-es2015-node5": "^1.2.0",
19 | "babel-preset-react": "6.11.1",
20 | "babel-preset-stage-2": "6.13.0",
21 | "bootstrap": "4.0.0-alpha.3",
22 | "clean-webpack-plugin": "0.1.10",
23 | "compass-mixins": "0.12.10",
24 | "copy-webpack-plugin": "3.0.1",
25 | "css-loader": "0.23.1",
26 | "express": "4.14.0",
27 | "express-handlebars": "3.0.0",
28 | "extract-text-webpack-plugin": "2.0.0-beta.4",
29 | "html-loader": "0.4.3",
30 | "html-webpack-plugin": "2.22.0",
31 | "image-webpack-loader": "2.0.0",
32 | "lodash.range": "^3.2.0",
33 | "ngrok": "2.2.2",
34 | "node-sass": "3.8.0",
35 | "postcss-loader": "0.9.1",
36 | "react": "15.3.1",
37 | "react-addons-shallow-compare": "15.3.1",
38 | "react-click-outside": "2.1.0",
39 | "react-document-title": "2.0.2",
40 | "react-dom": "15.3.1",
41 | "react-hot-loader": "3.0.0-beta.2",
42 | "react-router": "2.8.1",
43 | "react-virtualized": "^7.0.0",
44 | "sass-loader": "4.0.0",
45 | "style-loader": "0.13.1",
46 | "webpack": "^2.1.0-beta.4",
47 | "webpack-dashboard": "git+https://github.com/kyle-ssg/webpack-dashboard.git#master",
48 | "webpack-dev-middleware": "1.7.0",
49 | "webpack-hot-middleware": "2.12.2"
50 | },
51 | "main": "dist/index",
52 | "directories": {
53 | "example": "example"
54 | },
55 | "peerDependencies": {
56 | "react": ">=0.14.0",
57 | "react-dom": ">=0.14.0",
58 | "react-addons-shallow-compare": ">=0.14.0",
59 | "react-virtualized": "^7.0.0"
60 | },
61 | "keywords": [
62 | "react-component",
63 | "scrollview",
64 | "infinite",
65 | "virtualized",
66 | "scroll",
67 | "react"
68 | ],
69 | "dependencies": {},
70 | "repository": {
71 | "type": "git",
72 | "url": "git+https://github.com/SolidStateGroup/react-virtualized-infinite-scroll.git"
73 | },
74 | "bugs": {
75 | "url": "https://github.com/SolidStateGroup/react-virtualized-infinite-scroll/issues"
76 | },
77 | "homepage": "https://github.com/SolidStateGroup/react-virtualized-infinite-scroll#readme"
78 | }
79 |
--------------------------------------------------------------------------------