diff --git a/Libraries/Experimental/WindowedListView.js b/Libraries/Experimental/WindowedListView.js deleted file mode 100644 index ec56b388ed1..00000000000 --- a/Libraries/Experimental/WindowedListView.js +++ /dev/null @@ -1,827 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @format - * @flow - */ - -'use strict'; - -const Batchinator = require('../Interaction/Batchinator'); -const IncrementalGroup = require('./IncrementalGroup'); -const React = require('react'); -const ScrollView = require('../Components/ScrollView/ScrollView'); -const StyleSheet = require('../StyleSheet/StyleSheet'); -const Systrace = require('../Performance/Systrace'); -const View = require('../Components/View/View'); -const ViewabilityHelper = require('../Lists/ViewabilityHelper'); - -const clamp = require('../Utilities/clamp'); -const deepDiffer = require('../Utilities/differ/deepDiffer'); -const infoLog = require('../Utilities/infoLog'); -const invariant = require('invariant'); -const nullthrows = require('nullthrows'); - -import type {NativeMethodsMixinType} from '../Renderer/shims/ReactNativeTypes'; - -const DEBUG = false; - -/** - * An experimental ListView implementation designed for efficient memory usage - * when rendering huge/infinite lists. It works by rendering a subset of rows - * and replacing offscreen rows with an empty spacer, which means that it has to - * re-render rows when scrolling back up. - * - * Note that rows must be the same height when they are re-mounted as when they - * are unmounted otherwise the content will jump around. This means that any - * state that affects the height, such as tap to expand, should be stored - * outside the row component to maintain continuity. - * - * This is not a drop-in replacement for `ListView` - many features are not - * supported, including section headers, dataSources, horizontal layout, etc. - * - * Row data should be provided as a simple array corresponding to rows. `===` - * is used to determine if a row has changed and should be re-rendered. - * - * Rendering is done incrementally one row at a time to minimize the amount of - * work done per JS event tick. Individual rows can also use - * to further break up the work and keep the app responsive and improve scroll - * perf if rows get exceedingly complex. - * - * Note that it's possible to scroll faster than rows can be rendered. Instead - * of showing the user a bunch of un-mounted blank space, WLV sets contentInset - * to prevent scrolling into unrendered areas. Supply the - * `renderWindowBoundaryIndicator` prop to indicate the boundary to the user, - * e.g. with a row placeholder. - */ -type Props = { - /** - * A simple array of data blobs that are passed to the renderRow function in - * order. Note there is no dataSource like in the standard `ListView`. - */ - data: Array<{rowKey: string, rowData: any}>, - /** - * Takes a data blob from the `data` array prop plus some meta info and should - * return a row. - */ - renderRow: ( - rowData: any, - sectionIdx: number, - rowIdx: number, - rowKey: string, - ) => ?React.Element, - /** - * Rendered when the list is scrolled faster than rows can be rendered. - */ - renderWindowBoundaryIndicator?: ( - showIndicator: boolean, - ) => ?React.Element, - /** - * Always rendered at the bottom of all the rows. - */ - renderFooter?: (showFooter: boolean) => ?React.Element, - /** - * Pipes through normal onScroll events from the underlying `ScrollView`. - */ - onScroll?: (event: Object) => void, - /** - * Called when the rows that are visible in the viewport change. - */ - onVisibleRowsChanged?: (firstIdx: number, count: number) => void, - /** - * Called when the viewability of rows changes, as defined by the - * `viewablePercentThreshold` prop. - */ - onViewableRowsChanged?: (viewableRows: Array) => void, - /** - * The percent of a row that must be visible to consider it "viewable". - */ - viewablePercentThreshold: number, - /** - * Number of rows to render on first mount. - */ - initialNumToRender: number, - /** - * Maximum number of rows to render while scrolling, i.e. the window size. - */ - maxNumToRender: number, - /** - * Number of rows to render beyond the viewport. Note that this combined with - * `maxNumToRender` and the number of rows that can fit in one screen will - * determine how many rows to render above the viewport. - */ - numToRenderAhead: number, - /** - * Used to log perf events for async row rendering. - */ - asyncRowPerfEventName?: string, - /** - * A function that returns the scrollable component in which the list rows - * are rendered. Defaults to returning a ScrollView with the given props. - */ - renderScrollComponent: (props: ?Object) => React.Element, - /** - * Use to disable incremental rendering when not wanted, e.g. to speed up initial render. - */ - disableIncrementalRendering: boolean, - /** - * This determines how frequently events such as scroll and layout can trigger a re-render. - */ - recomputeRowsBatchingPeriod: number, - /** - * Called when rows will be mounted/unmounted. Mounted rows always form a contiguous block so it - * is expressed as a range of start plus count. - */ - onMountedRowsWillChange?: (firstIdx: number, count: number) => void, - /** - * Change this when you want to make sure the WindowedListView will re-render, for example when - * the result of `renderScrollComponent` might change. It will be compared in - * `shouldComponentUpdate`. - */ - shouldUpdateToken?: string, -}; - -type State = { - boundaryIndicatorHeight?: number, - firstRow: number, - lastRow: number, -}; -class WindowedListView extends React.Component { - /** - * Recomputing which rows to render is batched up and run asynchronously to avoid wasteful updates, - * e.g. from multiple layout updates in rapid succession. - */ - _computeRowsToRenderBatcher: Batchinator; - _firstVisible: number = -1; - _lastVisible: number = -1; - _scrollOffsetY: number = 0; - _isScrolling: boolean = false; - _frameHeight: number = 0; - _rowFrames: {[key: string]: Object} = {}; - _rowRenderMode: {[key: string]: null | 'async' | 'sync'} = {}; - _rowFramesDirty: boolean = false; - _hasCalledOnEndReached: boolean = false; - _willComputeRowsToRender: boolean = false; - _viewableRows: Array = []; - _cellsInProgress: Set = new Set(); - _scrollRef: ?ScrollView; - _viewabilityHelper: ViewabilityHelper; - - static defaultProps: {| - disableIncrementalRendering: boolean, - initialNumToRender: number, - maxNumToRender: number, - numToRenderAhead: number, - recomputeRowsBatchingPeriod: number, - renderScrollComponent: (props: any) => React.Node, - viewablePercentThreshold: number, - |} = { - initialNumToRender: 10, - maxNumToRender: 30, - numToRenderAhead: 10, - viewablePercentThreshold: 50, - renderScrollComponent: props => , - disableIncrementalRendering: false, - recomputeRowsBatchingPeriod: 10, // This should capture most events that happen within a frame - }; - - constructor(props: Props) { - super(props); - invariant( - this.props.numToRenderAhead < this.props.maxNumToRender, - 'WindowedListView: numToRenderAhead must be less than maxNumToRender', - ); - this._computeRowsToRenderBatcher = new Batchinator( - () => this._computeRowsToRender(this.props), - this.props.recomputeRowsBatchingPeriod, - ); - this._viewabilityHelper = new ViewabilityHelper({ - viewAreaCoveragePercentThreshold: this.props.viewablePercentThreshold, - }); - this.state = { - firstRow: 0, - lastRow: - Math.min(this.props.data.length, this.props.initialNumToRender) - 1, - }; - } - getScrollResponder(): ?ScrollView { - return ( - this._scrollRef && - this._scrollRef.getScrollResponder && - // $FlowFixMe - it actually returns ScrollView & ScrollResponder.Mixin - this._scrollRef.getScrollResponder() - ); - } - shouldComponentUpdate(newProps: Props, newState: State): boolean { - DEBUG && infoLog('WLV: shouldComponentUpdate...'); - if (newState !== this.state) { - DEBUG && infoLog(' yes: ', {newState, oldState: this.state}); - return true; - } - for (const key in newProps) { - if (key !== 'data' && newProps[key] !== this.props[key]) { - DEBUG && infoLog(' yes, non-data prop change: ', {key}); - return true; - } - } - const newDataSubset = newProps.data.slice( - newState.firstRow, - newState.lastRow + 1, - ); - const prevDataSubset = this.props.data.slice( - this.state.firstRow, - this.state.lastRow + 1, - ); - if (newDataSubset.length !== prevDataSubset.length) { - DEBUG && - infoLog(' yes, subset length: ', { - newLen: newDataSubset.length, - oldLen: prevDataSubset.length, - }); - return true; - } - for (let idx = 0; idx < newDataSubset.length; idx++) { - if ( - newDataSubset[idx].rowData !== prevDataSubset[idx].rowData || - newDataSubset[idx].rowKey !== prevDataSubset[idx].rowKey - ) { - DEBUG && - infoLog(' yes, data change: ', { - idx, - new: newDataSubset[idx], - old: prevDataSubset[idx], - }); - return true; - } - } - DEBUG && infoLog(' knope'); - return false; - } - UNSAFE_componentWillReceiveProps() { - this._computeRowsToRenderBatcher.schedule(); - } - _onMomentumScrollEnd = (e: Object) => { - this._onScroll(e); - }; - _getFrameMetrics = (index: number): ?{length: number, offset: number} => { - const frame = this._rowFrames[this.props.data[index].rowKey]; - return frame && {length: frame.height, offset: frame.y}; - }; - _onScroll = (e: Object) => { - const newScrollY = e.nativeEvent.contentOffset.y; - this._isScrolling = this._scrollOffsetY !== newScrollY; - this._scrollOffsetY = newScrollY; - this._frameHeight = e.nativeEvent.layoutMeasurement.height; - // We don't want to enqueue any updates if any cells are in the middle of an incremental render, - // because it would just be wasted work. - if (this._cellsInProgress.size === 0) { - this._computeRowsToRenderBatcher.schedule(); - } - if ( - this.props.onViewableRowsChanged && - Object.keys(this._rowFrames).length - ) { - const viewableRows = this._viewabilityHelper.computeViewableItems( - this.props.data.length, - e.nativeEvent.contentOffset.y, - e.nativeEvent.layoutMeasurement.height, - this._getFrameMetrics, - ); - if (deepDiffer(viewableRows, this._viewableRows)) { - this._viewableRows = viewableRows; - nullthrows(this.props.onViewableRowsChanged)(this._viewableRows); - } - } - this.props.onScroll && this.props.onScroll(e); - }; - // Caller does the diffing so we don't have to. - _onNewLayout = (params: {rowKey: string, layout: Object}) => { - const {rowKey, layout} = params; - if (DEBUG) { - const prev = this._rowFrames[rowKey] || {}; - infoLog('record layout for row: ', { - k: rowKey, - h: layout.height, - y: layout.y, - x: layout.x, - hp: prev.height, - yp: prev.y, - }); - if (this._rowFrames[rowKey]) { - const deltaY = Math.abs(this._rowFrames[rowKey].y - layout.y); - const deltaH = Math.abs(this._rowFrames[rowKey].height - layout.height); - if (deltaY > 2 || deltaH > 2) { - const dataEntry = this.props.data.find( - datum => datum.rowKey === rowKey, - ); - console.warn('layout jump: ', { - dataEntry, - prevLayout: this._rowFrames[rowKey], - newLayout: layout, - }); - } - } - } - this._rowFrames[rowKey] = {...layout, offscreenLayoutDone: true}; - this._rowFramesDirty = true; - if (this._cellsInProgress.size === 0) { - this._computeRowsToRenderBatcher.schedule(); - } - }; - _onWillUnmountCell = (rowKey: string) => { - if (this._rowFrames[rowKey]) { - this._rowFrames[rowKey].offscreenLayoutDone = false; - this._rowRenderMode[rowKey] = null; - } - }; - /** - * This is used to keep track of cells that are in the process of rendering. If any cells are in - * progress, then other updates are skipped because they will just be wasted work. - */ - _onProgressChange = ({ - rowKey, - inProgress, - }: { - rowKey: string, - inProgress: boolean, - }) => { - if (inProgress) { - this._cellsInProgress.add(rowKey); - } else { - this._cellsInProgress.delete(rowKey); - } - }; - componentWillUnmount() { - this._computeRowsToRenderBatcher.dispose(); - } - _computeRowsToRender(props: Object): void { - const totalRows = props.data.length; - if (totalRows === 0) { - this._updateVisibleRows(-1, -1); - this.setState({ - firstRow: 0, - lastRow: -1, - }); - return; - } - const rowFrames = this._rowFrames; - let firstVisible = -1; - let lastVisible = 0; - let lastRow = clamp(0, this.state.lastRow, totalRows - 1); - const top = this._scrollOffsetY; - const bottom = top + this._frameHeight; - for (let idx = 0; idx < lastRow; idx++) { - const frame = rowFrames[props.data[idx].rowKey]; - if (!frame) { - // No frame - sometimes happens when they come out of order, so just wait for the rest. - return; - } - if (frame.y + frame.height > top && firstVisible < 0) { - firstVisible = idx; - } - if (frame.y < bottom) { - lastVisible = idx; - } else { - break; - } - } - if (firstVisible === -1) { - firstVisible = 0; - } - this._updateVisibleRows(firstVisible, lastVisible); - - // Unfortunately, we can't use to simplify our increment logic in this function - // because we need to make sure that cells are rendered in the right order one at a time when - // scrolling back up. - - const numRendered = lastRow - this.state.firstRow + 1; - // Our last row target that we will approach incrementally - const targetLastRow = clamp( - numRendered - 1, // Don't reduce numRendered when scrolling back up - lastVisible + props.numToRenderAhead, // Primary goal - totalRows - 1, // Don't render past the end - ); - // Increment the last row one at a time per JS event loop - if (targetLastRow > this.state.lastRow) { - lastRow++; - } else if (targetLastRow < this.state.lastRow) { - lastRow--; - } - // Once last row is set, figure out the first row - const firstRow = Math.max( - 0, // Don't render past the top - lastRow - props.maxNumToRender + 1, // Don't exceed max to render - lastRow - numRendered, // Don't render more than 1 additional row - ); - if (lastRow >= totalRows) { - // It's possible that the number of rows decreased by more than one - // increment could compensate for. Need to make sure we don't render more - // than one new row at a time, but don't want to render past the end of - // the data. - lastRow = totalRows - 1; - } - if (props.onEndReached) { - // Make sure we call onEndReached exactly once every time we reach the - // end. Resets if scroll back up and down again. - const willBeAtTheEnd = lastRow === totalRows - 1; - if (willBeAtTheEnd && !this._hasCalledOnEndReached) { - props.onEndReached(); - this._hasCalledOnEndReached = true; - } else { - // If lastRow is changing, reset so we can call onEndReached again - this._hasCalledOnEndReached = this.state.lastRow === lastRow; - } - } - const rowsShouldChange = - firstRow !== this.state.firstRow || lastRow !== this.state.lastRow; - if (this._rowFramesDirty || rowsShouldChange) { - if (rowsShouldChange) { - props.onMountedRowsWillChange && - props.onMountedRowsWillChange(firstRow, lastRow - firstRow + 1); - infoLog('WLV: row render range will change:', { - firstRow, - firstVis: this._firstVisible, - lastVis: this._lastVisible, - lastRow, - }); - } - this._rowFramesDirty = false; - this.setState({firstRow, lastRow}); - } - } - _updateVisibleRows(newFirstVisible: number, newLastVisible: number) { - if (this.props.onVisibleRowsChanged) { - if ( - this._firstVisible !== newFirstVisible || - this._lastVisible !== newLastVisible - ) { - this.props.onVisibleRowsChanged( - newFirstVisible, - newLastVisible - newFirstVisible + 1, - ); - } - } - this._firstVisible = newFirstVisible; - this._lastVisible = newLastVisible; - } - render(): React.Node { - const {firstRow} = this.state; - const lastRow = clamp(0, this.state.lastRow, this.props.data.length - 1); - const rowFrames = this._rowFrames; - const rows = []; - let spacerHeight = 0; - // Incremental rendering is a tradeoff between throughput and responsiveness. When we have - // plenty of buffer (say 50% of the target), we render incrementally to keep the app responsive. - // If we are dangerously low on buffer (say below 25%) we always disable incremental to try to - // catch up as fast as possible. In the middle, we only disable incremental while scrolling - // since it's unlikely the user will try to press a button while scrolling. We also ignore the - // "buffer" size when we are bumped up against the edge of the available data. - const firstBuffer = - firstRow === 0 ? Infinity : this._firstVisible - firstRow; - const lastBuffer = - lastRow === this.props.data.length - 1 - ? Infinity - : lastRow - this._lastVisible; - const minBuffer = Math.min(firstBuffer, lastBuffer); - const disableIncrementalRendering = - this.props.disableIncrementalRendering || - (this._isScrolling && minBuffer < this.props.numToRenderAhead * 0.5) || - minBuffer < this.props.numToRenderAhead * 0.25; - // Render mode is sticky while the component is mounted. - for (let ii = firstRow; ii <= lastRow; ii++) { - const rowKey = this.props.data[ii].rowKey; - if ( - this._rowRenderMode[rowKey] === 'sync' || - (disableIncrementalRendering && this._rowRenderMode[rowKey] !== 'async') - ) { - this._rowRenderMode[rowKey] = 'sync'; - } else { - this._rowRenderMode[rowKey] = 'async'; - } - } - for (let ii = firstRow; ii <= lastRow; ii++) { - const rowKey = this.props.data[ii].rowKey; - if (!rowFrames[rowKey]) { - break; // if rowFrame missing, no following ones will exist so quit early - } - // Look for the first row where offscreen layout is done (only true for mounted rows) or it - // will be rendered synchronously and set the spacer height such that it will offset all the - // unmounted rows before that one using the saved frame data. - if ( - rowFrames[rowKey].offscreenLayoutDone || - this._rowRenderMode[rowKey] === 'sync' - ) { - if (ii > 0) { - const prevRowKey = this.props.data[ii - 1].rowKey; - const frame = rowFrames[prevRowKey]; - spacerHeight = frame ? frame.y + frame.height : 0; - } - break; - } - } - let showIndicator = false; - if ( - spacerHeight > (this.state.boundaryIndicatorHeight || 0) && - this.props.renderWindowBoundaryIndicator - ) { - showIndicator = true; - spacerHeight -= this.state.boundaryIndicatorHeight || 0; - } - DEBUG && infoLog('render top spacer with height ', spacerHeight); - rows.push(); - if (this.props.renderWindowBoundaryIndicator) { - // Always render it, even if removed, so that we can get the height right away and don't waste - // time creating/ destroying it. Should see if there is a better spinner option that is not as - // expensive. - rows.push( - { - const layout = e.nativeEvent.layout; - if (layout.height !== this.state.boundaryIndicatorHeight) { - this.setState({boundaryIndicatorHeight: layout.height}); - } - }}> - {this.props.renderWindowBoundaryIndicator(showIndicator)} - , - ); - } - for (let idx = firstRow; idx <= lastRow; idx++) { - const rowKey = this.props.data[idx].rowKey; - const includeInLayout = - this._rowRenderMode[rowKey] === 'sync' || - (this._rowFrames[rowKey] && - this._rowFrames[rowKey].offscreenLayoutDone); - rows.push( - , - ); - } - const lastRowKey = this.props.data[lastRow].rowKey; - const showFooter = - this._rowFrames[lastRowKey] && - this._rowFrames[lastRowKey].offscreenLayoutDone && - lastRow === this.props.data.length - 1; - if (this.props.renderFooter) { - rows.push( - - {this.props.renderFooter(showFooter)} - , - ); - } - if (this.props.renderWindowBoundaryIndicator) { - rows.push( - { - const layout = e.nativeEvent.layout; - if (layout.height !== this.state.boundaryIndicatorHeight) { - this.setState({boundaryIndicatorHeight: layout.height}); - } - }}> - {this.props.renderWindowBoundaryIndicator(!showFooter)} - , - ); - } - // Prevent user from scrolling into empty space of unmounted rows. - const contentInset = {top: firstRow === 0 ? 0 : -spacerHeight}; - return this.props.renderScrollComponent({ - scrollEventThrottle: 50, - removeClippedSubviews: true, - ...this.props, - contentInset, - ref: ref => { - this._scrollRef = ref; - }, - onScroll: this._onScroll, - onMomentumScrollEnd: this._onMomentumScrollEnd, - children: rows, - }); - } -} - -// performance testing id, unique for each component mount cycle -let g_perf_update_id = 0; - -type CellProps = { - /** - * Row-specific data passed to renderRow and used in shouldComponentUpdate with === - */ - rowData: mixed, - rowKey: string, - /** - * Renders the actual row contents. - */ - renderRow: ( - rowData: mixed, - sectionIdx: number, - rowIdx: number, - rowKey: string, - ) => ?React.Element, - /** - * Index of the row, passed through to other callbacks. - */ - rowIndex: number, - /** - * Used for marking async begin/end events for row rendering. - */ - asyncRowPerfEventName: ?string, - /** - * Initially false to indicate the cell should be rendered "offscreen" with position: absolute so - * that incremental rendering doesn't cause things to jump around. Once onNewLayout is called - * after offscreen rendering has completed, includeInLayout will be set true and the finished cell - * can be dropped into place. - * - * This is coordinated outside this component so the parent can synchronize this re-render with - * managing the placeholder sizing. - */ - includeInLayout: boolean, - /** - * Updates the parent with the latest layout. Only called when incremental rendering is done and - * triggers the parent to re-render this row with includeInLayout true. - */ - onNewLayout: (params: {rowKey: string, layout: Object}) => void, - /** - * Used to track when rendering is in progress so the parent can avoid wasteful re-renders that - * are just going to be invalidated once the cell finishes. - */ - onProgressChange: (progress: {rowKey: string, inProgress: boolean}) => void, - /** - * Used to invalidate the layout so the parent knows it needs to compensate for the height in the - * placeholder size. - */ - onWillUnmount: (rowKey: string) => void, -}; -class CellRenderer extends React.Component { - _containerRef: NativeMethodsMixinType; - _offscreenRenderDone = false; - _timeout = 0; - _lastLayout: ?Object = null; - _perfUpdateID: number = 0; - _asyncCookie: any; - _includeInLayoutLatch: boolean = false; - UNSAFE_componentWillMount() { - if (this.props.asyncRowPerfEventName) { - this._perfUpdateID = g_perf_update_id++; - this._asyncCookie = Systrace.beginAsyncEvent( - this.props.asyncRowPerfEventName + this._perfUpdateID, - ); - infoLog( - // $FlowFixMe(>=0.28.0) - `perf_asynctest_${this.props.asyncRowPerfEventName}_start ${ - this._perfUpdateID - } ` + `${Date.now()}`, - ); - } - if (this.props.includeInLayout) { - this._includeInLayoutLatch = true; - } - this.props.onProgressChange({rowKey: this.props.rowKey, inProgress: true}); - } - _onLayout = e => { - const layout = e.nativeEvent.layout; - const layoutChanged = deepDiffer(this._lastLayout, layout); - this._lastLayout = layout; - if (!this._offscreenRenderDone || !layoutChanged) { - return; // Don't send premature or duplicate updates - } - this.props.onNewLayout({ - rowKey: this.props.rowKey, - layout, - }); - }; - _updateParent() { - invariant(!this._offscreenRenderDone, 'should only finish rendering once'); - this._offscreenRenderDone = true; - - // If this is not called before calling onNewLayout, the number of inProgress cells will remain - // non-zero, and thus the onNewLayout call will not fire the needed state change update. - this.props.onProgressChange({rowKey: this.props.rowKey, inProgress: false}); - - // If an onLayout event hasn't come in yet, then we skip here and assume it will come in later. - // This happens when Incremental is disabled and _onOffscreenRenderDone is called faster than - // layout can happen. - this._lastLayout && - this.props.onNewLayout({ - rowKey: this.props.rowKey, - layout: this._lastLayout, - }); - - DEBUG && - infoLog('\n >>>>> display row ' + this.props.rowIndex + '\n\n\n'); - if (this.props.asyncRowPerfEventName) { - // Note this doesn't include the native render time but is more accurate than also including - // the JS render time of anything that has been queued up. - Systrace.endAsyncEvent( - this.props.asyncRowPerfEventName + this._perfUpdateID, - this._asyncCookie, - ); - infoLog( - // $FlowFixMe(>=0.28.0) - `perf_asynctest_${this.props.asyncRowPerfEventName}_end ${ - this._perfUpdateID - } ` + `${Date.now()}`, - ); - } - } - _onOffscreenRenderDone = () => { - DEBUG && infoLog('_onOffscreenRenderDone for row ' + this.props.rowIndex); - if (this._includeInLayoutLatch) { - this._updateParent(); // rendered straight into layout, so no need to flush - } else { - this._timeout = setTimeout(() => this._updateParent(), 1); // Flush any pending layout events. - } - }; - componentWillUnmount() { - /* $FlowFixMe(>=0.63.0 site=react_native_fb) This comment suppresses an - * error found when Flow v0.63 was deployed. To see the error delete this - * comment and run Flow. */ - clearTimeout(this._timeout); - this.props.onProgressChange({rowKey: this.props.rowKey, inProgress: false}); - this.props.onWillUnmount(this.props.rowKey); - } - UNSAFE_componentWillReceiveProps(newProps) { - if (newProps.includeInLayout && !this.props.includeInLayout) { - invariant( - this._offscreenRenderDone, - 'Should never try to add to layout before render done', - ); - this._includeInLayoutLatch = true; // Once we render in layout, make sure it sticks. - this._containerRef.setNativeProps({style: styles.include}); - } - } - shouldComponentUpdate(newProps: CellProps) { - return newProps.rowData !== this.props.rowData; - } - _setRef = ref => { - /* $FlowFixMe(>=0.53.0 site=react_native_fb,react_native_oss) This comment - * suppresses an error when upgrading Flow's support for React. To see the - * error delete this comment and run Flow. */ - this._containerRef = ref; - }; - render() { - let debug; - if (DEBUG) { - infoLog('render cell ' + this.props.rowIndex); - const Text = require('../Text/Text'); - debug = Row: {this.props.rowIndex}; - } - const style = this._includeInLayoutLatch ? styles.include : styles.remove; - return ( - - - {debug} - {this.props.renderRow( - this.props.rowData, - 0, - this.props.rowIndex, - this.props.rowKey, - )} - {debug} - - - ); - } -} - -const removedXOffset = DEBUG ? 123 : 0; - -const styles = StyleSheet.create({ - include: { - position: 'relative', - left: 0, - right: 0, - opacity: 1, - }, - remove: { - position: 'absolute', - left: removedXOffset, - right: -removedXOffset, - opacity: DEBUG ? 0.1 : 0, - }, - debug: { - backgroundColor: 'lightblue', - }, -}); - -module.exports = WindowedListView;