From e12e56894702476c7ac5e90bc2a41e4507472881 Mon Sep 17 00:00:00 2001 From: arunreddy10 <56394019+arunreddy10@users.noreply.github.com> Date: Mon, 4 May 2020 12:09:45 +0530 Subject: [PATCH] Remove the initial empty render of the scrollview if a layoutSize is passed (#450) * Adding a layout size prop to take initial size of RLV and use this render to remove initial shift by making opacity 0 * Removing visibility related changes * Moving componentDidMount logic into constructor * Renaming layoutSize to scrollViewSize prop * Fixing initial offset * Handling the case when initial offset is provided and preventing force render when the onLayout is called for the first time * Version bump * Version bump to beta before publishing * Moved willMountCompat logic to constructor and removed acceptableLayoutDelta check from gridlayoutmanager * Undo acceptable relayout delta changes * Renaming getContextFromProvider method * version bump * fix: removed extra changes * Renaming prop to scrollViewSize and using cachedLayouts in forceRender scenario * Renaming prop to layoutSize and moving forceFullRender to separate block Co-authored-by: Talha Naqvi --- README.md | 1 + package.json | 2 +- src/core/RecyclerListView.tsx | 168 ++++++++++++++++++++++------------ 3 files changed, 111 insertions(+), 60 deletions(-) diff --git a/README.md b/README.md index ece814e5..fe4e05c3 100644 --- a/README.md +++ b/README.md @@ -107,6 +107,7 @@ not be as fast. | optimizeForInsertDeleteAnimations | No | boolean | Enables you to utilize layout animations better by unmounting removed items | | style | No | object | To pass down style to inner ScrollView | | scrollViewProps | No | object | For all props that need to be proxied to inner/external scrollview. Put them in an object and they'll be spread and passed down. | +| layoutSize | No | Dimension | Will prevent the initial empty render required to compute the size of the listview and use these dimensions to render list items in the first render itself. This is useful for cases such as server side rendering. The prop canChangeSize has to be set to true if the size can be changed after rendering. Note that this is not the scroll view size and is used solely for layouting. | For full feature set have a look at prop definitions of [RecyclerListView](https://github.com/Flipkart/recyclerlistview/blob/21049cc89ad606ec9fe8ea045dc73732ff29eac9/src/core/RecyclerListView.tsx#L540-L634) (bottom of the file). All `ScrollView` features like `RefreshControl` also work out of the box. diff --git a/package.json b/package.json index c0371418..8c2ac0f8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "recyclerlistview", - "version": "3.0.0", + "version": "3.0.1", "description": "The listview that you need and deserve. It was built for performance, uses cell recycling to achieve smooth scrolling.", "main": "dist/reactnative/index.js", "types": "dist/reactnative/index.d.ts", diff --git a/src/core/RecyclerListView.tsx b/src/core/RecyclerListView.tsx index a0c6ee17..b46260ba 100644 --- a/src/core/RecyclerListView.tsx +++ b/src/core/RecyclerListView.tsx @@ -91,6 +91,7 @@ export interface RecyclerListViewProps { onVisibleIndicesChanged?: TOnItemStatusChanged; renderFooter?: () => JSX.Element | JSX.Element[] | null; externalScrollView?: { new(props: ScrollViewDefaultProps): BaseScrollView }; + layoutSize?: Dimension; initialOffset?: number; initialRenderIndex?: number; scrollThrottle?: number; @@ -164,14 +165,21 @@ export default class RecyclerListView

{ - this.scrollToOffset(offset.x, offset.y, false); - }, 0); - } + this._processInitialOffset(); this._processOnEndReached(); this._checkAndChangeLayouts(this.props); if (this.props.dataProvider.getSize() === 0) { @@ -208,6 +205,13 @@ export default class RecyclerListView

0) { - this._initialOffset = offset; - if (this.props.onRecreate) { - this.props.onRecreate({ lastOffset: this._initialOffset }); - } - this.props.contextProvider.remove(uniqueKey + Constants.CONTEXT_PROVIDER_OFFSET_KEY_SUFFIX); - } - if (this.props.forceNonDeterministicRendering) { - const cachedLayouts = this.props.contextProvider.get(uniqueKey + Constants.CONTEXT_PROVIDER_LAYOUT_KEY_SUFFIX) as string; - if (cachedLayouts && typeof cachedLayouts === "string") { - this._cachedLayouts = JSON.parse(cachedLayouts).layoutArray; - this.props.contextProvider.remove(uniqueKey + Constants.CONTEXT_PROVIDER_LAYOUT_KEY_SUFFIX); - } - } - } - } - } - public scrollToIndex(index: number, animate?: boolean): void { const layoutManager = this._virtualRenderer.getLayoutManager(); if (layoutManager) { @@ -381,6 +362,44 @@ export default class RecyclerListView

{ + this.scrollToOffset(offset.x, offset.y, false); + }, 0); + } + } + + private _getContextFromContextProvider(props: RecyclerListViewProps): void { + if (props.contextProvider) { + const uniqueKey = props.contextProvider.getUniqueKey(); + if (uniqueKey) { + const offset = props.contextProvider.get(uniqueKey + Constants.CONTEXT_PROVIDER_OFFSET_KEY_SUFFIX); + if (typeof offset === "number" && offset > 0) { + this._initialOffset = offset; + if (props.onRecreate) { + props.onRecreate({ lastOffset: this._initialOffset }); + } + props.contextProvider.remove(uniqueKey + Constants.CONTEXT_PROVIDER_OFFSET_KEY_SUFFIX); + } + if (props.forceNonDeterministicRendering) { + const cachedLayouts = props.contextProvider.get(uniqueKey + Constants.CONTEXT_PROVIDER_LAYOUT_KEY_SUFFIX) as string; + if (cachedLayouts && typeof cachedLayouts === "string") { + this._cachedLayouts = JSON.parse(cachedLayouts).layoutArray; + props.contextProvider.remove(uniqueKey + Constants.CONTEXT_PROVIDER_LAYOUT_KEY_SUFFIX); + } + } + } + } + } + private _checkAndChangeLayouts(newProps: RecyclerListViewProps, forceFullRender?: boolean): void { this._params.isHorizontal = newProps.isHorizontal; this._params.itemCount = newProps.dataProvider.getSize(); @@ -389,7 +408,7 @@ export default class RecyclerListView

= 0) { const layoutManager = this._virtualRenderer.getLayoutManager(); if (layoutManager) { @@ -433,6 +459,9 @@ export default class RecyclerListView

{ + if (!this.props.canChangeSize && this.props.layoutSize) { + return; + } const hasHeightChanged = this._layout.height !== layout.height; const hasWidthChanged = this._layout.width !== layout.width; this._layout.height = layout.height; @@ -442,7 +471,7 @@ export default class RecyclerListView

{ - this.setState(() => { - return { renderStack: stack }; - }); + if (!this._initStateIfRequired(stack)) { + this.setState(() => { + return { renderStack: stack }; + }); + } } - private _initTrackers(): void { - this._assertDependencyPresence(this.props); - if (this.props.onVisibleIndexesChanged) { + private _initTrackers(props: RecyclerListViewProps): void { + this._assertDependencyPresence(props); + if (props.onVisibleIndexesChanged) { throw new CustomError(RecyclerListViewExceptions.usingOldVisibleIndexesChangedParam); } - if (this.props.onVisibleIndicesChanged) { - this._virtualRenderer.attachVisibleItemsListener(this.props.onVisibleIndicesChanged!); + if (props.onVisibleIndicesChanged) { + this._virtualRenderer.attachVisibleItemsListener(props.onVisibleIndicesChanged!); } this._params = { - initialOffset: this._initialOffset ? this._initialOffset : this.props.initialOffset, - initialRenderIndex: this.props.initialRenderIndex, - isHorizontal: this.props.isHorizontal, - itemCount: this.props.dataProvider.getSize(), - renderAheadOffset: this.props.renderAheadOffset, + initialOffset: this._initialOffset ? this._initialOffset : props.initialOffset, + initialRenderIndex: props.initialRenderIndex, + isHorizontal: props.isHorizontal, + itemCount: props.dataProvider.getSize(), + renderAheadOffset: props.renderAheadOffset, }; this._virtualRenderer.setParamsAndDimensions(this._params, this._layout); - const layoutManager = this.props.layoutProvider.newLayoutManager(this._layout, this.props.isHorizontal, this._cachedLayouts); + const layoutManager = props.layoutProvider.newLayoutManager(this._layout, props.isHorizontal, this._cachedLayouts); this._virtualRenderer.setLayoutManager(layoutManager); - this._virtualRenderer.setLayoutProvider(this.props.layoutProvider); + this._virtualRenderer.setLayoutProvider(props.layoutProvider); this._virtualRenderer.init(); const offset = this._virtualRenderer.getInitialOffset(); const contentDimension = layoutManager.getContentDimension(); if ((offset.y > 0 && contentDimension.height > this._layout.height) || (offset.x > 0 && contentDimension.width > this._layout.width)) { this._pendingScrollToOffset = offset; - this.setState({}); + if (!this._initStateIfRequired()) { + this.setState({}); + } } else { - this._virtualRenderer.startViewabilityTracker(this._getWindowCorrection(offset.x, offset.y, this.props)); + this._virtualRenderer.startViewabilityTracker(this._getWindowCorrection(offset.x, offset.y, props)); } } @@ -675,6 +719,12 @@ RecyclerListView.propTypes = { //Specify the initial item index you want rendering to start from. Preferred over initialOffset if both are specified. initialRenderIndex: PropTypes.number, + //Specify the estimated size of the recyclerlistview to render the list items in the first pass. If provided, recyclerlistview will + //use these dimensions to fill in the items in the first render. If not provided, recyclerlistview will first render with no items + //and then fill in the items based on the size given by its onLayout event. canChangeSize can be set to true to relayout items when + //the size changes. + layoutSize: PropTypes.object, + //iOS only. Scroll throttle duration. scrollThrottle: PropTypes.number,