UNPKG

react-virtualized

Version:

React components for efficiently rendering large, scrollable lists and tabular data

187 lines (158 loc) 5.91 kB
import { Component, PropTypes } from 'react'; import shallowCompare from 'react-addons-shallow-compare'; /** * Higher-order component that manages lazy-loading for "infinite" data. * This component decorates a virtual component and just-in-time prefetches rows as a user scrolls. * It is intended as a convenience component; fork it if you'd like finer-grained control over data-loading. */ var InfiniteLoader = function (_Component) { babelHelpers.inherits(InfiniteLoader, _Component); function InfiniteLoader(props, context) { babelHelpers.classCallCheck(this, InfiniteLoader); var _this = babelHelpers.possibleConstructorReturn(this, Object.getPrototypeOf(InfiniteLoader).call(this, props, context)); _this._onRowsRendered = _this._onRowsRendered.bind(_this); _this._registerChild = _this._registerChild.bind(_this); return _this; } babelHelpers.createClass(InfiniteLoader, [{ key: 'render', value: function render() { var children = this.props.children; return children({ onRowsRendered: this._onRowsRendered, registerChild: this._registerChild }); } }, { key: 'shouldComponentUpdate', value: function shouldComponentUpdate(nextProps, nextState) { return shallowCompare(this, nextProps, nextState); } }, { key: '_onRowsRendered', value: function _onRowsRendered(_ref) { var _this2 = this; var startIndex = _ref.startIndex; var stopIndex = _ref.stopIndex; var _props = this.props; var isRowLoaded = _props.isRowLoaded; var loadMoreRows = _props.loadMoreRows; var rowsCount = _props.rowsCount; var threshold = _props.threshold; this._lastRenderedStartIndex = startIndex; this._lastRenderedStopIndex = stopIndex; var unloadedRanges = scanForUnloadedRanges({ isRowLoaded: isRowLoaded, startIndex: Math.max(0, startIndex - threshold), stopIndex: Math.min(rowsCount, stopIndex + threshold) }); unloadedRanges.forEach(function (unloadedRange) { var promise = loadMoreRows(unloadedRange); if (promise) { promise.then(function () { // Refresh the visible rows if any of them have just been loaded. // Otherwise they will remain in their unloaded visual state. if (isRangeVisible({ lastRenderedStartIndex: _this2._lastRenderedStartIndex, lastRenderedStopIndex: _this2._lastRenderedStopIndex, startIndex: unloadedRange.startIndex, stopIndex: unloadedRange.stopIndex })) { if (_this2._registeredChild) { _this2._registeredChild.forceUpdate(); } } }); } }); } }, { key: '_registerChild', value: function _registerChild(registeredChild) { this._registeredChild = registeredChild; } }]); return InfiniteLoader; }(Component); /** * Determines if the specified start/stop range is visible based on the most recently rendered range. */ InfiniteLoader.propTypes = { /** * Function respondible for rendering a virtualized component. * This function should implement the following signature: * ({ onRowsRendered, registerChild }) => PropTypes.element * * The specified :onRowsRendered function should be passed through to the child's :onRowsRendered property. * The :registerChild callback should be set as the virtualized component's :ref. */ children: PropTypes.func.isRequired, /** * Function responsible for tracking the loaded state of each row. * It should implement the following signature: (index: number): boolean */ isRowLoaded: PropTypes.func.isRequired, /** * Callback to be invoked when more rows must be loaded. * It should implement the following signature: ({ startIndex, stopIndex }): Promise * The returned Promise should be resolved once row data has finished loading. * It will be used to determine when to refresh the list with the newly-loaded data. * This callback may be called multiple times in reaction to a single scroll event. */ loadMoreRows: PropTypes.func.isRequired, /** * Number of rows in list; can be arbitrary high number if actual number is unknown. */ rowsCount: PropTypes.number.isRequired, /** * Threshold at which to pre-fetch data. * A threshold X means that data will start loading when a user scrolls within X rows. * This value defaults to 15. */ threshold: PropTypes.number.isRequired }; InfiniteLoader.defaultProps = { rowsCount: 0, threshold: 15 }; export default InfiniteLoader; export function isRangeVisible(_ref2) { var lastRenderedStartIndex = _ref2.lastRenderedStartIndex; var lastRenderedStopIndex = _ref2.lastRenderedStopIndex; var startIndex = _ref2.startIndex; var stopIndex = _ref2.stopIndex; return !(startIndex > lastRenderedStopIndex || stopIndex < lastRenderedStartIndex); } /** * Returns all of the ranges within a larger range that contain unloaded rows. */ export function scanForUnloadedRanges(_ref3) { var isRowLoaded = _ref3.isRowLoaded; var startIndex = _ref3.startIndex; var stopIndex = _ref3.stopIndex; var unloadedRanges = []; var rangeStartIndex = null; var rangeStopIndex = null; for (var i = startIndex; i <= stopIndex; i++) { var loaded = isRowLoaded(i); if (!loaded) { rangeStopIndex = i; if (rangeStartIndex === null) { rangeStartIndex = i; } } else if (rangeStopIndex !== null) { unloadedRanges.push({ startIndex: rangeStartIndex, stopIndex: rangeStopIndex }); rangeStartIndex = rangeStopIndex = null; } } if (rangeStopIndex !== null) { unloadedRanges.push({ startIndex: rangeStartIndex, stopIndex: rangeStopIndex }); } return unloadedRanges; }