Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@ import type {

import SectionListBaseExample from './SectionListBaseExample';
import * as React from 'react';
import {useRef, useState} from 'react';
import {useEffect, useRef, useState} from 'react';
import {StyleSheet, View} from 'react-native';

const BASE_VIEWABILITY_CONFIG = {
minimumViewTime: 1000,
viewAreaCoveragePercentThreshold: 100,
};
const VIEWABILITY_OBSERVATION_TIME_MS =
BASE_VIEWABILITY_CONFIG.minimumViewTime * 2;

export function SectionList_BaseOnViewableItemsChanged(props: {
offScreen?: ?boolean,
Expand All @@ -30,7 +32,28 @@ export function SectionList_BaseOnViewableItemsChanged(props: {
waitForInteraction?: ?boolean,
}): React.Node {
const {offScreen, horizontal, useScrollRefScroll, waitForInteraction} = props;
const [observationComplete, setObservationComplete] = useState(false);
const [output, setOutput] = useState('');
const observationTimeoutRef = useRef<?TimeoutID>(null);
useEffect(() => {
return () => {
if (observationTimeoutRef.current != null) {
clearTimeout(observationTimeoutRef.current);
}
};
}, []);
const onListLayout =
offScreen === true
? () => {
if (observationTimeoutRef.current != null) {
clearTimeout(observationTimeoutRef.current);
}
setObservationComplete(false);
observationTimeoutRef.current = setTimeout(() => {
setObservationComplete(true);
}, VIEWABILITY_OBSERVATION_TIME_MS);
}
: undefined;
const viewabilityConfig: ViewabilityConfig = {
...BASE_VIEWABILITY_CONFIG,
waitForInteraction: waitForInteraction ?? false,
Expand All @@ -49,6 +72,7 @@ export function SectionList_BaseOnViewableItemsChanged(props: {
),
viewabilityConfig,
horizontal,
onLayout: onListLayout,
};
const ref = useRef<any>(null);
const onTest =
Expand All @@ -63,6 +87,11 @@ export function SectionList_BaseOnViewableItemsChanged(props: {
ref={ref}
exampleProps={exampleProps}
onTest={onTest}
testContainerTestID={
observationComplete
? 'viewability_observation_complete'
: 'test_container'
}
testOutput={output}>
{offScreen === true ? <View style={styles.offScreen} /> : null}
</SectionListBaseExample>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ type Props = Readonly<{
// $FlowFixMe[unclear-type]
exampleProps: Partial<React.ElementConfig<typeof SectionList<any>>>,
onTest?: ?() => void,
testContainerTestID?: ?string,
testLabel?: ?string,
testOutput?: ?string,
children?: ?React.Node,
Expand All @@ -88,7 +89,9 @@ const SectionListBaseExample: component(
return (
<View style={styles.container}>
{props.testOutput != null ? (
<View testID="test_container" style={styles.testContainer}>
<View
testID={props.testContainerTestID ?? 'test_container'}
style={styles.testContainer}>
<Text style={styles.output} numberOfLines={1} testID="output">
{props.testOutput}
</Text>
Expand Down
11 changes: 9 additions & 2 deletions packages/virtualized-lists/Lists/VirtualizedList.js
Original file line number Diff line number Diff line change
Expand Up @@ -1242,6 +1242,7 @@ class VirtualizedList extends StateSafePureComponent<
_hasWarned: {[string]: boolean} = {};
_headerLength = 0;
_hiPriInProgress: boolean = false; // flag to prevent infinite hiPri cell limit update
_hasZeroArea: boolean = false;
_indicesToKeys: Map<number, string> = new Map();
_lastFocusedCellKey: ?string = null;
_nestedChildLists: ChildListCollection<VirtualizedList> =
Expand Down Expand Up @@ -1420,6 +1421,9 @@ class VirtualizedList extends StateSafePureComponent<
}

_onLayout = (e: LayoutChangeEvent) => {
const hadZeroArea = this._hasZeroArea;
const {height, width} = e.nativeEvent.layout;
this._hasZeroArea = height === 0 || width === 0;
if (this._isNestedWithSameOrientation()) {
// Need to adjust our scroll metrics to be relative to our containing
// VirtualizedList before we can make claims about list item viewability
Expand All @@ -1431,6 +1435,9 @@ class VirtualizedList extends StateSafePureComponent<
}
this.props.onLayout && this.props.onLayout(e);
this._scheduleCellsToRenderUpdate();
if (hadZeroArea !== this._hasZeroArea) {
this._updateViewableItems(this.props, this.state.cellsAroundViewport);
}
this._maybeCallOnEdgeReached();
};

Expand Down Expand Up @@ -2031,14 +2038,14 @@ class VirtualizedList extends StateSafePureComponent<
) {
// If we have any pending scroll updates it means that the scroll metrics
// are out of date and we should not call any of the visibility callbacks.
if (this.state.pendingScrollUpdateCount > 0) {
if (this.state.pendingScrollUpdateCount > 0 && !this._hasZeroArea) {
return;
}
this._viewabilityTuples.forEach(tuple => {
tuple.viewabilityHelper.onUpdate(
props,
this._scrollMetrics.offset,
this._scrollMetrics.visibleLength,
this._hasZeroArea ? 0 : this._scrollMetrics.visibleLength,
this._listMetrics,
this._createViewToken,
tuple.onViewableItemsChanged,
Expand Down
51 changes: 51 additions & 0 deletions packages/virtualized-lists/Lists/__tests__/VirtualizedList-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,57 @@ describe('VirtualizedList', () => {
);
});

it('does not report horizontal items after the list collapses vertically', async () => {
const data = [{key: 'i1'}];
const onViewableItemsChanged = jest.fn();
const viewabilityConfig = {
minimumViewTime: 1000,
viewAreaCoveragePercentThreshold: 100,
};
let component;
await act(() => {
component = create(
<VirtualizedList
data={data}
getItem={(items, index) => items[index]}
getItemCount={items => items.length}
getItemLayout={(items, index) => ({
index,
length: 100,
offset: index * 100,
})}
horizontal={true}
onViewableItemsChanged={onViewableItemsChanged}
renderItem={({item}) => <item value={item.key} />}
viewabilityConfig={viewabilityConfig}
/>,
);
});

const instance = component.getInstance();
await act(async () => {
instance._onLayout({
nativeEvent: {layout: {height: 100, width: 300}, zoomScale: 1},
});
instance._onScroll({
timeStamp: 1000,
nativeEvent: {
contentInset: {bottom: 0, left: 0, right: 0, top: 0},
contentOffset: {x: 0, y: 0},
contentSize: {height: 100, width: 300},
layoutMeasurement: {height: 100, width: 300},
zoomScale: 1,
},
});
instance._onLayout({
nativeEvent: {layout: {height: 0, width: 300}, zoomScale: 1},
});
await jest.runAllTimersAsync();
});

expect(onViewableItemsChanged).not.toHaveBeenCalled();
});

it('getScrollRef for case where it returns a ScrollView', async () => {
const listRef = createRef(null);

Expand Down
Loading