Skip to content

Conversation

@ArrayZoneYour
Copy link

@ArrayZoneYour ArrayZoneYour commented Oct 13, 2025

Reintroduce #782

The PR fix a corner case when ViewRenderItem componentDidUpdate 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)

  1. onLayout event.nativeEvent.layout.height === this.props.height will be true, so _this._dim won't be updated, it will keep { width: 0, height: 0 }
  2. _scheduleForceSizeUpdateTimer will emit after first element onLayout event emit, it will use { width: 0, height: 0 } rather than estimated / real size to update size.
  3. Because minHeight:1, the first item will only show a hairline

Screenshot:

image

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);
            }
        }
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant