fix: update ViewRenderer dim when estimate size is equal with real #799
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Reintroduce #782
The PR fix a corner case when
ViewRenderItemcomponentDidUpdate lifecycle emit with layoutManager rebuild.Under React Native Legacy Architecture, onLayout event will be emit one by one in MessageQueue. It will bring problem when list items is less or JS execute fast.
When estimated height / width is equal with real and layoutManager rebuild (eg. numColumns changes from 0 => 2)
event.nativeEvent.layout.height === this.props.heightwill be true, so _this._dim won't be updated, it will keep{ width: 0, height: 0 }_scheduleForceSizeUpdateTimerwill emit after first element onLayout event emit, it will use{ width: 0, height: 0 }rather than estimated / real size to update size.minHeight:1, the first item will only show a hairlineScreenshot:
Video (Before):
Simulator.Screen.Recording.-.iPhone.15.Pro.-.2025-10-13.at.21.18.20.mov
Video (After):
Simulator.Screen.Recording.-.iPhone.15.Pro.-.2025-10-13.at.22.03.29.mov
Repo:
https://github.com/ArrayZoneYour/react-native-flash-list-repo-1013
export default class ViewRenderer extends BaseViewRenderer<any> { private _dim: Dimension = { width: 0, height: 0 }; private _viewRef: React.Component<ViewProperties, React.ComponentState> | null = null; private _layoutManagerRef?: LayoutManager; public componentDidUpdate(): void { super.componentDidUpdate(); if (this.props.layoutProvider && this._layoutManagerRef) { + if (this.props.layoutProvider.getLayoutManager() !== this._layoutManagerRef) { + this._layoutManagerRef = this.props.layoutProvider.getLayoutManager(); + this._scheduleForceSizeUpdateTimer(); } } } public componentDidMount(): void { super.componentDidMount(); if (this.props.layoutProvider) { this._layoutManagerRef = this.props.layoutProvider.getLayoutManager(); } } protected getRef(): object | null { return this._viewRef; } private _renderItemContainer(props: object, parentProps: ViewRendererProps<any>, children: React.ReactNode): React.ReactNode { return (this.props.renderItemContainer && this.props.renderItemContainer(props, parentProps, children)) || (<View {...props}>{children}</View>); } private _setRef = (view: React.Component<ViewProperties, React.ComponentState> | null): void => { this._viewRef = view; } private _onLayout = (event: LayoutChangeEvent): void => { //Preventing layout thrashing in super fast scrolls where RN messes up onLayout event const xDiff = Math.abs(this.props.x - event.nativeEvent.layout.x); const yDiff = Math.abs(this.props.y - event.nativeEvent.layout.y); // this._dim should be updated when estimate height / width equal with real height / width + this._dim.height = event.nativeEvent.layout.height; + this._dim.width = event.nativeEvent.layout.width; if (xDiff < 1 && yDiff < 1 && (this.props.height !== event.nativeEvent.layout.height || this.props.width !== event.nativeEvent.layout.width)) { - this._dim.height = event.nativeEvent.layout.height; - this._dim.width = event.nativeEvent.layout.width; if (this.props.onSizeChanged) { this.props.onSizeChanged(this._dim, this.props.index); } } if (this.props.onItemLayout) { this.props.onItemLayout(this.props.index); } } private _scheduleForceSizeUpdateTimer = () => { // forceSizeUpdate calls onSizeChanged which can only be called when non-deterministic rendering is used. if (!this.props.forceNonDeterministicRendering) { return; } const oldDim = {...this._dim}; setTimeout(() => { this._forceSizeUpdate(oldDim); }, 32); } private _forceSizeUpdate = (dim: Dimension): void => { if (dim.width === this._dim.width && dim.height === this._dim.height) { if (this.isRendererMounted && this.props.onSizeChanged) { this.props.onSizeChanged(this._dim, this.props.index); } } } }