* Copyright (c) 2025 Huawei Technologies Co., Ltd.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import React, {useRef, useState} from 'react';
import {
RefreshControl,
Text,
TouchableOpacity,
View,
ViewabilityConfig,
VirtualizedList,
} from 'react-native';
import {TestSuite} from '@rnoh/testerino';
import {Button, MockedVideoPlayer, TestCase} from '../components';
type OnScrollToIndexFailed = {
index: number;
highestMeasuredFrameIndex: number;
averageItemLength: number;
};
type ItemData = {
id: string;
title: string;
};
const getItem = (_data: unknown, index: number): ItemData => ({
id: index.toString(),
title: `Item ${index + 1}`,
});
const getItemCountVirtualized = (_data: unknown): number => 50;
const Item = ({title}: {title: string}) => (
<View style={{height: 48, padding: 16, borderWidth: 1}}>
<Text style={{width: '100%', height: 24}}>{title}</Text>
</View>
);
const HorizontalItem = ({title}: {title: string}) => (
<View style={{borderWidth: 1, width: 48, height: '100%'}}>
<Text style={{width: '100%', height: '100%'}}>{title}</Text>
</View>
);
export function VirtualizedListTest() {
return (
<TestSuite name="VirtualizedList">
<TestCase.Example itShould="display list of 3 items">
<VirtualizedList<number[]>
style={{height: 64}}
data={[1, 2, 3]}
getItem={(data, idx) => data[idx]}
getItemCount={() => 3}
renderItem={({item}) => (
<View style={{height: 48, padding: 16}}>
<Text style={{width: '100%', height: 24}}>{item}</Text>
</View>
)}
keyExtractor={(_, index) => String(index)}
/>
</TestCase.Example>
<TestCase.Manual
modal
itShould="trigger onStartReached event when start of the content is within half the visible length of the list"
initialState={-1}
arrange={({setState}) => {
const data = [1, 2, 3, 4, 5];
return (
<VirtualizedList
data={data}
getItem={(items: number[], idx: number) => items[idx]}
getItemCount={() => data.length}
renderItem={({item}: {item: number}) => (
<View style={{height: 100, padding: 16, borderWidth: 1}}>
<Text style={{width: '100%', height: 24}}>{item}</Text>
</View>
)}
style={{height: 200}}
onStartReachedThreshold={0.5}
onStartReached={({
distanceFromStart,
}: {
distanceFromStart: number;
}) => {
setState(distanceFromStart);
}}
/>
);
}}
assert={({state, expect}) => {
expect(state).to.be.lessThanOrEqual(100);
}}
/>
<TestCase.Manual
modal
itShould="display event sent to by onScrollToIndexFailed when pressing the button before scrolling"
initialState={undefined}
arrange={({state, setState}) => {
return (
<VirtualizedListOnScrollToIndexFailed
state={state}
setState={setState}
/>
);
}}
assert={({state, expect}) => {
expect(state).to.be.not.undefined;
expect(state).to.have.all.keys([
'index',
'highestMeasuredFrameIndex',
'averageItemLength',
]);
}}
/>
<TestCase.Example modal itShould="invert the list">
<InvertedVirtualizedListTest />
</TestCase.Example>
<TestCase.Example modal itShould="start at the 81st item">
<InitialScrollIndexTest />
</TestCase.Example>
<TestSuite name="ref">
<TestCase.Example
modal
itShould="scroll to the element with the index 10 (Item 11) - scrollToIndex()">
<VirtualizedListScrollToIndexTest />
</TestCase.Example>
<TestCase.Example
modal
itShould="scroll to the specific element (Item 3) - scrollToItem()">
<VirtualizedListScrollToItemTest />
</TestCase.Example>
<TestCase.Manual
modal
itShould="scroll to the end of the list - scrollToEnd()"
initialState={false}
arrange={({state, setState}) => {
return (
<VirtualizedListGetScrollToEnd
state={state}
setState={setState}
/>
);
}}
assert={({state, expect}) => {
expect(state).to.be.true;
}}
/>
<TestCase.Manual
modal
itShould="get the node number - getScrollableNode()"
initialState={undefined}
arrange={({state, setState}) => {
return (
<VirtualizedListGetScrollableNode
state={state}
setState={setState}
/>
);
}}
assert={({state, expect}) => {
expect(state).to.be.not.undefined;
expect(state).to.be.an('number');
}}
/>
<TestCase.Manual
modal
itShould="get the scroll ref - getScrollRef()"
initialState={undefined}
arrange={({state, setState}) => {
return (
<VirtualizedListGetScrollRef state={state} setState={setState} />
);
}}
assert={({state, expect}) => {
expect(state).to.be.not.undefined;
}}
/>
</TestSuite>
<TestCase.Manual
modal
itShould="click (call on ref.recordInteraction()) on button before first scroll should trigger onViewableItemsChanged and change the first two items background color to blue"
initialState={[]}
arrange={({state, setState}) => {
return (
<VirtualizedListRecordInteractionTest
state={state}
setState={setState}
/>
);
}}
assert={({state, expect}) => {
expect(state).to.be.not.empty;
expect(state).to.be.an('array');
expect(state).to.have.lengthOf(2);
}}
/>
<TestCase.Example
modal
itShould="change background color of visible items after scrolling slightly (to blue when fully visible and lightblue when at least 20% is visible)">
<VirtualizedListTestViewabiliyConfigCallbackPairs />
</TestCase.Example>
<TestCase.Example
modal
itShould="change background color of fully visible items after 2 seconds">
<VirtualizedListViewabilityConfigViewTime />
</TestCase.Example>
<TestCase.Example
modal
itShould="display even items with a lightgray background and odd items should have lightblue background (CellRenderedComponent)">
<VirtualizedListCellRendererComponent />
</TestCase.Example>
<TestCase.Example
modal
itShould='display "Separator" text on lightgray background between items'>
<VirtualizedListItemSeparatorTest />
</TestCase.Example>
<TestCase.Example
modal
itShould='display "Empty Component" text on lightgray background when data is empty'>
<VirtualizedListListEmptyComponentTest />
</TestCase.Example>
<TestCase.Example
modal
itShould='display "Item Component" text on lightgray background'>
<VirtualizedListListItemComponent />
</TestCase.Example>
<TestCase.Example
modal
itShould='display "List Footer Component" text on lightgray background at the end of the list'>
<VirtualizedListListFooterComponent />
</TestCase.Example>
<TestCase.Example
modal
itShould='display "List Footer Component" text on red background at the end of the list'>
<VirtualizedListListFooterComponentStyles />
</TestCase.Example>
<TestCase.Example
modal
itShould='display "List Header Component" text on small lightgray background on red background at the start of the list'>
<VirtualizedListListHeaderComponent />
</TestCase.Example>
<TestCase.Example itShould="display debugging scroll bars (debug = true)">
<VirtualizedListDebugTest />
</TestCase.Example>
<TestCase.Example
modal
itShould="change background color of selected items to red">
<VirtualizedListExtraDataTest />
</TestCase.Example>
<TestCase.Example modal itShould="display items horizontally">
<VirtualizedListHorizontalTest />
</TestCase.Example>
<TestCase.Example
modal
itShould="display items horizontally and invert the list">
<VirtualizedListHorizontalInvertedTest />
</TestCase.Example>
<TestCase.Example
modal
itShould="display 'refreshing' on red background when pulling down (onRefresh method)">
<VirtualizedListOnRefreshTest />
</TestCase.Example>
<TestCase.Example modal itShould="display persistent scrollbar">
<VirtualizedListPersistentScrollbarTest />
</TestCase.Example>
<TestCase.Example modal itShould="render cell in 2 second batches">
<VirtualizedListUpdateCellsBatchingPeriodTest />
</TestCase.Example>
<TestCase.Example
modal
itShould="display refreshing indicator when pulling down (refreshControl)">
<VirtualizedListRefreshControlTest />
</TestCase.Example>
</TestSuite>
);
}
function VirtualizedListOnScrollToIndexFailed({
state,
setState,
}: {
state: OnScrollToIndexFailed | undefined;
setState: (state: any) => void;
}) {
const ref = useRef<VirtualizedList<ItemData>>(null);
const handleOnPress = () => {
if (ref.current) {
ref.current.scrollToIndex({index: 20, animated: true});
}
};
return (
<>
<Button label="Scroll to NOT_EXISTING index" onPress={handleOnPress} />
<View style={{height: 50, backgroundColor: 'lightblue'}}>
<Text>{state ? JSON.stringify(state) : ''}</Text>
</View>
<VirtualizedList
initialNumToRender={5}
windowSize={5}
ref={ref}
style={{height: 128}}
getItem={getItem}
getItemCount={getItemCountVirtualized}
renderItem={({item}: {item: ItemData}) => <Item title={item.title} />}
keyExtractor={(item: ItemData) => item.id}
onScrollToIndexFailed={(failInfo: OnScrollToIndexFailed) => {
// @ts-ignore
setState(failInfo);
}}
/>
</>
);
}
const GENERATED_DATA = Array.from({length: 100}, (_, index) => ({
id: String(index),
title: `Item ${index + 1}`,
}));
function VirtualizedListScrollToIndexTest() {
const ref = useRef<VirtualizedList<ItemData>>(null);
const handleOnPress = () => {
if (ref.current) {
ref.current?.scrollToIndex({index: 10, animated: true});
}
};
return (
<>
<Button label="Scroll to index = 10" onPress={handleOnPress} />
<VirtualizedList
ref={ref}
style={{height: 128}}
getItem={(_, index: number) => GENERATED_DATA[index]}
getItemCount={() => GENERATED_DATA.length}
renderItem={({item}: {item: ItemData}) => <Item title={item.title} />}
keyExtractor={(item: ItemData) => item.id}
/>
</>
);
}
function VirtualizedListScrollToItemTest() {
const ref = useRef<VirtualizedList<ItemData>>(null);
const handleOnPress = () => {
if (ref.current) {
ref.current.scrollToItem({item: GENERATED_DATA[2], animated: true});
}
};
return (
<>
<Button label="Scroll to item = 3" onPress={handleOnPress} />
<VirtualizedList
ref={ref}
style={{height: 256}}
data={GENERATED_DATA}
getItem={(_, index: number) => GENERATED_DATA[index]}
getItemCount={() => GENERATED_DATA.length}
getItemLayout={(_, index: number) => ({
length: 48,
offset: 48 * index,
index,
})}
renderItem={({item}: {item: ItemData}) => <Item title={item.title} />}
keyExtractor={(item: ItemData) => item.id}
/>
</>
);
}
function InvertedVirtualizedListTest() {
return (
<VirtualizedList
style={{height: 256}}
data={GENERATED_DATA}
getItem={(_, index: number) => GENERATED_DATA[index]}
getItemCount={() => GENERATED_DATA.length}
getItemLayout={(_, index: number) => ({
length: 48,
offset: 48 * index,
index,
})}
inverted
renderItem={({item}: {item: ItemData}) => (
<TouchableOpacity onPress={() => console.log(item.title)}>
<Item title={item.title} />
</TouchableOpacity>
)}
keyExtractor={(item: ItemData) => item.id}
/>
);
}
function InitialScrollIndexTest() {
return (
<VirtualizedList
style={{height: 256}}
data={GENERATED_DATA}
getItem={(_, index: number) => GENERATED_DATA[index]}
getItemCount={() => GENERATED_DATA.length}
getItemLayout={(_, index: number) => ({
length: 48,
offset: 48 * index,
index,
})}
initialScrollIndex={80}
renderItem={({item}: {item: ItemData}) => <Item title={item.title} />}
keyExtractor={(item: ItemData) => item.id}
/>
);
}
function VirtualizedListGetScrollableNode({
state,
setState,
}: {
state: number | undefined;
setState: (state: any) => void;
}) {
const ref = useRef<VirtualizedList<ItemData>>(null);
const handleOnPress = () => {
if (ref.current) {
// @ts-ignore - getScrollableNode() is not in the type definition
// in react-native repository but it is in the documentation
const node = ref.current.getScrollableNode();
setState(node);
}
};
return (
<>
<Button label="Get ScrollableNode" onPress={handleOnPress} />
<View style={{height: 50, backgroundColor: 'lightblue'}}>
<Text>{`ScrollableNode = ${state}`}</Text>
</View>
<VirtualizedList
ref={ref}
style={{height: 128}}
getItem={getItem}
getItemCount={getItemCountVirtualized}
renderItem={({item}: {item: ItemData}) => <Item title={item.title} />}
keyExtractor={(item: ItemData) => item.id}
/>
</>
);
}
function VirtualizedListGetScrollRef({
state,
setState,
}: {
state: boolean | undefined;
setState: (state: any) => void;
}) {
const ref = useRef<VirtualizedList<ItemData>>(null);
const handleOnPress = () => {
if (ref.current) {
const scrollRef = ref.current.getScrollRef();
setState(Boolean(scrollRef));
}
};
return (
<>
<Button label="Get ScrollRef" onPress={handleOnPress} />
<View style={{height: 50, backgroundColor: 'lightblue'}}>
<Text>{`ScrollRef is defined: ${state}`}</Text>
</View>
<VirtualizedList
ref={ref}
style={{height: 128}}
getItem={getItem}
getItemCount={getItemCountVirtualized}
renderItem={({item}: {item: ItemData}) => <Item title={item.title} />}
keyExtractor={(item: ItemData) => item.id}
/>
</>
);
}
function VirtualizedListGetScrollToEnd({
state,
setState,
}: {
state: boolean | undefined;
setState: (state: any) => void;
}) {
const ref = useRef<VirtualizedList<ItemData>>(null);
const handleOnPress = () => {
if (ref.current) {
ref.current.scrollToEnd({animated: true});
}
};
return (
<>
<Button label="Scroll to the end" onPress={handleOnPress} />
<View style={{height: 50, backgroundColor: 'lightblue'}}>
<Text>{`End is reached: ${state}`}</Text>
</View>
<VirtualizedList
ref={ref}
style={{height: 128}}
getItem={getItem}
getItemCount={getItemCountVirtualized}
renderItem={({item}: {item: ItemData}) => <Item title={item.title} />}
keyExtractor={(item: ItemData) => item.id}
onEndReached={() => setState(true)}
/>
</>
);
}
interface ViewToken<TItem> {
item: TItem;
key: string;
index: number | null;
isViewable: boolean;
section?: any | undefined;
}
type OnViewableItemsChangedType<TItem> = {
viewableItems: Array<ViewToken<TItem>>;
changed: Array<ViewToken<TItem>>;
};
const deafultViewabilityConfig: ViewabilityConfig = {
// Nothing is considered viewable until the user scrolls or `recordInteraction`
// is called after render.
waitForInteraction: true,
// minimum amount of time (in milliseconds) that an item must be physically viewable
// before the viewability callback will be fired
minimumViewTime: 100,
// viewAreaCoveragePercentThreshold: 100,
itemVisiblePercentThreshold: 70,
};
function VirtualizedListRecordInteractionTest({
state,
setState,
}: {
state: string[];
setState: (state: any) => void;
}) {
const [visibleItems, setVisibleItems] = useState<string[]>(state);
const ref = useRef<VirtualizedList<ItemData>>(null);
const handleOnPress = () => {
if (ref.current) {
ref.current.recordInteraction();
}
};
const onViewableItemsChanged = ({
viewableItems,
}: OnViewableItemsChangedType<ItemData>) => {
const newVisibleItems = viewableItems.map(
viewableItem => viewableItem.item.id,
);
setVisibleItems(newVisibleItems);
setState(newVisibleItems);
};
return (
<View style={{height: 600}}>
<View style={{marginBottom: 10}}>
<Button label="Record interaction" onPress={handleOnPress} />
<Text style={{padding: 10}}>
Visible Items are: {JSON.stringify(visibleItems)}
</Text>
</View>
<VirtualizedList
ref={ref}
style={{height: 128}}
getItem={getItem}
getItemCount={getItemCountVirtualized}
renderItem={({item}: {item: ItemData}) => (
<MockedVideoPlayer
itemId={item.id}
playMockVideo={visibleItems.includes(item.id)}
/>
)}
keyExtractor={(item: ItemData) => item.id}
viewabilityConfig={deafultViewabilityConfig}
onViewableItemsChanged={onViewableItemsChanged}
/>
</View>
);
}
const firstViewabilityConfig: ViewabilityConfig = {
waitForInteraction: true,
minimumViewTime: 100,
itemVisiblePercentThreshold: 100,
};
const secondViewabilityConfig: ViewabilityConfig = {
waitForInteraction: true,
minimumViewTime: 100,
itemVisiblePercentThreshold: 20,
};
function VirtualizedListTestViewabiliyConfigCallbackPairs() {
const [firstVisibleItems, setFirstVisibleItems] = useState<string[]>([]);
const [secondVisibleItems, setSecondVisibleItems] = useState<string[]>([]);
const viewabilityConfigCallbackPairs = [
{
viewabilityConfig: firstViewabilityConfig,
onViewableItemsChanged: ({
viewableItems,
}: OnViewableItemsChangedType<ItemData>) => {
const newFirstVisibleItems = viewableItems.map(
viewableItem => viewableItem.item.id,
);
setFirstVisibleItems(newFirstVisibleItems);
},
},
{
viewabilityConfig: secondViewabilityConfig,
onViewableItemsChanged: ({
viewableItems,
}: OnViewableItemsChangedType<ItemData>) => {
const newSecondVisibleItems = viewableItems.map(
viewableItem => viewableItem.item.id,
);
setSecondVisibleItems(newSecondVisibleItems);
},
},
];
return (
<View style={{height: 600}}>
<View style={{marginBottom: 10}}>
<Text>
First threshold {firstViewabilityConfig.itemVisiblePercentThreshold}%{' '}
visible items are: {JSON.stringify(firstVisibleItems)}
</Text>
<Text>
second threshold {secondViewabilityConfig.itemVisiblePercentThreshold}
% visible items are: {JSON.stringify(secondVisibleItems)}
</Text>
</View>
<VirtualizedList
style={{height: 128}}
getItem={getItem}
getItemCount={getItemCountVirtualized}
renderItem={({item}: {item: ItemData}) => (
<MockedVideoPlayer
height={300}
itemId={item.id}
playMockVideo={firstVisibleItems.includes(item.id)}
prefetchMockVideo={secondVisibleItems.includes(item.id)}
/>
)}
keyExtractor={(item: ItemData) => item.id}
viewabilityConfigCallbackPairs={viewabilityConfigCallbackPairs}
/>
</View>
);
}
const thirdViewabilityConfig: ViewabilityConfig = {
waitForInteraction: true,
minimumViewTime: 2000,
itemVisiblePercentThreshold: 100,
};
function VirtualizedListViewabilityConfigViewTime() {
const [visibleItems, setVisibleItems] = useState<string[]>([]);
const onViewableItemsChanged = ({
viewableItems,
}: OnViewableItemsChangedType<ItemData>) => {
const newVisibleItems = viewableItems.map(
viewableItem => viewableItem.item.id,
);
setVisibleItems(newVisibleItems);
};
return (
<View style={{height: 600}}>
<View style={{marginBottom: 10}}>
<Text style={{padding: 10}}>
Visible Items after {thirdViewabilityConfig.minimumViewTime}ms are:{' '}
{JSON.stringify(visibleItems)}
</Text>
</View>
<VirtualizedList
style={{height: 128}}
getItem={getItem}
getItemCount={getItemCountVirtualized}
renderItem={({item}: {item: ItemData}) => (
<MockedVideoPlayer
itemId={item.id}
playMockVideo={visibleItems.includes(item.id)}
/>
)}
keyExtractor={(item: ItemData) => item.id}
viewabilityConfig={thirdViewabilityConfig}
onViewableItemsChanged={onViewableItemsChanged}
/>
</View>
);
}
function VirtualizedListCellRendererComponent() {
return (
<VirtualizedList
style={{height: 128}}
data={GENERATED_DATA}
getItem={(_, index: number) => GENERATED_DATA[index]}
getItemCount={() => GENERATED_DATA.length}
getItemLayout={(_, index: number) => ({
length: 48,
offset: 48 * index,
index,
})}
renderItem={({item}: {item: ItemData}) => <Item title={item.title} />}
keyExtractor={(item: ItemData) => item.id}
CellRendererComponent={({
index,
children,
style,
}: {
index: number;
children: React.ReactNode;
style: any;
}) => {
return (
<View
style={{
...style,
backgroundColor: index % 2 === 0 ? 'lightblue' : 'lightgray',
}}>
{children}
</View>
);
}}
/>
);
}
function VirtualizedListItemSeparatorTest() {
return (
<VirtualizedList
style={{height: 128}}
data={GENERATED_DATA}
getItem={(_, index: number) => GENERATED_DATA[index]}
getItemCount={() => GENERATED_DATA.length}
getItemLayout={(_, index: number) => ({
length: 48,
offset: 48 * index,
index,
})}
renderItem={({item}: {item: ItemData}) => <Item title={item.title} />}
keyExtractor={(item: ItemData) => item.id}
ItemSeparatorComponent={() => (
<View
style={{
height: 20,
backgroundColor: 'lightgray',
alignSelf: 'center',
width: '90%',
}}>
<Text>Separator</Text>
</View>
)}
/>
);
}
function VirtualizedListListEmptyComponentTest() {
return (
<VirtualizedList
style={{height: 128}}
data={[]}
getItem={(_, index: number) => GENERATED_DATA[index]}
getItemCount={() => 0}
renderItem={({item}: {item: ItemData}) => <Item title={item.title} />}
keyExtractor={(item: ItemData) => item.id}
ListEmptyComponent={() => (
<View
style={{
backgroundColor: 'lightgray',
alignSelf: 'center',
width: '90%',
height: 100,
}}>
<Text>Empty Component</Text>
</View>
)}
/>
);
}
function VirtualizedListListItemComponent() {
return (
<VirtualizedList
style={{height: 128}}
data={GENERATED_DATA}
getItem={(_, index: number) => GENERATED_DATA[index]}
getItemCount={() => GENERATED_DATA.length}
getItemLayout={(_, index: number) => ({
length: 48,
offset: 48 * index,
index,
})}
// @ts-ignore
ListItemComponent={props => (
<View
style={{
backgroundColor: 'lightgray',
alignSelf: 'center',
width: '90%',
height: 100,
borderBottomWidth: 1,
justifyContent: 'center',
}}>
<Text style={{textAlign: 'center'}}>Item Component</Text>
<Text style={{textAlign: 'center'}}>{props.item.title}</Text>
</View>
)}
/>
);
}
function VirtualizedListListFooterComponent() {
return (
<VirtualizedList
style={{height: 128}}
data={GENERATED_DATA}
getItem={(_, index: number) => GENERATED_DATA[index]}
getItemCount={() => GENERATED_DATA.length}
getItemLayout={(_, index: number) => ({
length: 48,
offset: 48 * index,
index,
})}
ListFooterComponent={() => (
<View
style={{
backgroundColor: 'lightgray',
alignSelf: 'center',
width: '90%',
height: 100,
borderBottomWidth: 1,
justifyContent: 'center',
}}>
<Text style={{textAlign: 'center'}}>List Footer Component</Text>
</View>
)}
renderItem={({item}: {item: ItemData}) => <Item title={item.title} />}
keyExtractor={(item: ItemData) => item.id}
/>
);
}
function VirtualizedListListFooterComponentStyles() {
return (
<VirtualizedList
style={{height: 128}}
data={GENERATED_DATA}
getItem={(_, index: number) => GENERATED_DATA[index]}
getItemCount={() => GENERATED_DATA.length}
getItemLayout={(_, index: number) => ({
length: 48,
offset: 48 * index,
index,
})}
ListFooterComponent={() => (
<View>
<Text style={{textAlign: 'center'}}>List Footer Component</Text>
</View>
)}
ListFooterComponentStyle={{
backgroundColor: 'red',
alignSelf: 'center',
width: '90%',
height: 100,
borderBottomWidth: 1,
justifyContent: 'center',
}}
renderItem={({item}: {item: ItemData}) => <Item title={item.title} />}
keyExtractor={(item: ItemData) => item.id}
/>
);
}
function VirtualizedListListHeaderComponent() {
return (
<VirtualizedList
style={{height: 128}}
data={GENERATED_DATA}
getItem={(_, index: number) => GENERATED_DATA[index]}
getItemCount={() => GENERATED_DATA.length}
getItemLayout={(_, index: number) => ({
length: 48,
offset: 48 * index,
index,
})}
ListHeaderComponent={() => (
<View
style={{
backgroundColor: 'lightgray',
}}>
<Text style={{textAlign: 'center'}}>List Header Component</Text>
</View>
)}
ListHeaderComponentStyle={{
backgroundColor: 'red',
alignSelf: 'center',
width: '90%',
height: 100,
borderBottomWidth: 1,
justifyContent: 'center',
}}
renderItem={({item}: {item: ItemData}) => <Item title={item.title} />}
keyExtractor={(item: ItemData) => item.id}
/>
);
}
function VirtualizedListDebugTest() {
return (
<VirtualizedList
debug
disableVirtualization
style={{height: 256}}
data={GENERATED_DATA}
getItem={(_, index: number) => GENERATED_DATA[index]}
getItemCount={() => GENERATED_DATA.length}
getItemLayout={(_, index: number) => ({
length: 48,
offset: 48 * index,
index,
})}
renderItem={({item}: {item: ItemData}) => <Item title={item.title} />}
keyExtractor={(item: ItemData) => item.id}
/>
);
}
const CustomItem = ({
title,
isSelected,
}: {
isSelected: boolean;
title: string;
}) => {
return (
<TouchableOpacity
style={{
backgroundColor: isSelected ? 'red' : 'lightgray',
}}>
<Item title={title} />
</TouchableOpacity>
);
};
function VirtualizedListExtraDataTest() {
const [items] = useState(GENERATED_DATA);
const [selectedId, setSelectedId] = useState<string | undefined>(undefined);
return (
<View>
<Button label="Select 5 item" onPress={() => setSelectedId('4')} />
<VirtualizedList
style={{height: 256}}
data={items}
getItem={(_, index: number) => items[index]}
getItemCount={() => items.length}
getItemLayout={(_, index: number) => ({
length: 48,
offset: 48 * index,
index,
})}
extraData={selectedId}
renderItem={({item}: {item: ItemData}) => (
<CustomItem isSelected={selectedId === item.id} title={item.title} />
)}
keyExtractor={(item: ItemData) => item.id}
/>
</View>
);
}
function VirtualizedListHorizontalTest() {
return (
<VirtualizedList
style={{height: 256}}
horizontal
data={GENERATED_DATA}
getItem={(_, index: number) => GENERATED_DATA[index]}
getItemCount={() => GENERATED_DATA.length}
getItemLayout={(_, index: number) => ({
length: 48,
offset: 48 * index,
index,
})}
renderItem={({item}: {item: ItemData}) => (
<HorizontalItem title={item.title} />
)}
keyExtractor={(item: ItemData) => item.id}
/>
);
}
function VirtualizedListHorizontalInvertedTest() {
return (
<VirtualizedList
style={{height: 256}}
horizontal
inverted
data={GENERATED_DATA}
getItem={(_, index: number) => GENERATED_DATA[index]}
getItemCount={() => GENERATED_DATA.length}
getItemLayout={(_, index: number) => ({
length: 48,
offset: 48 * index,
index,
})}
renderItem={({item}: {item: ItemData}) => (
<HorizontalItem title={item.title} />
)}
keyExtractor={(item: ItemData) => item.id}
/>
);
}
function VirtualizedListOnRefreshTest() {
const [refreshing, setRefreshing] = useState(false);
const handleOnRefresh = () => {
setRefreshing(true);
setTimeout(() => {
setRefreshing(false);
}, 1000);
};
return (
<View>
<View
style={{
padding: 10,
backgroundColor: refreshing ? 'red' : 'transparent',
}}>
<Text>{refreshing ? 'isRefreshing' : 'Refresh!'}</Text>
</View>
<VirtualizedList
style={{height: 256}}
onRefresh={handleOnRefresh}
refreshing={refreshing}
data={GENERATED_DATA}
getItem={(_, index: number) => GENERATED_DATA[index]}
getItemCount={() => GENERATED_DATA.length}
getItemLayout={(_, index: number) => ({
length: 48,
offset: 48 * index,
index,
})}
renderItem={({item}: {item: ItemData}) => <Item title={item.title} />}
keyExtractor={(item: ItemData) => item.id}
/>
</View>
);
}
function VirtualizedListPersistentScrollbarTest() {
return (
<VirtualizedList
style={{height: 256}}
persistentScrollbar
data={GENERATED_DATA}
getItem={(_, index: number) => GENERATED_DATA[index]}
getItemCount={() => GENERATED_DATA.length}
getItemLayout={(_, index: number) => ({
length: 48,
offset: 48 * index,
index,
})}
renderItem={({item}: {item: ItemData}) => <Item title={item.title} />}
keyExtractor={(item: ItemData) => item.id}
/>
);
}
function VirtualizedListUpdateCellsBatchingPeriodTest() {
return (
<VirtualizedList
style={{height: 128}}
data={GENERATED_DATA}
getItem={(_, index: number) => GENERATED_DATA[index]}
getItemCount={() => GENERATED_DATA.length}
getItemLayout={(_, index: number) => ({
length: 48,
offset: 48 * index,
index,
})}
renderItem={({item}: {item: ItemData}) => <Item title={item.title} />}
keyExtractor={(item: ItemData) => item.id}
updateCellsBatchingPeriod={2000}
maxToRenderPerBatch={1}
/>
);
}
function VirtualizedListRefreshControlTest() {
const [refreshing, setRefreshing] = useState(false);
const handleOnRefresh = () => {
setRefreshing(true);
setTimeout(() => {
setRefreshing(false);
}, 1000);
};
return (
<VirtualizedList
style={{height: 256}}
refreshControl={
<RefreshControl refreshing={refreshing} onRefresh={handleOnRefresh} />
}
data={GENERATED_DATA}
getItem={(_, index: number) => GENERATED_DATA[index]}
getItemCount={() => GENERATED_DATA.length}
getItemLayout={(_, index: number) => ({
length: 48,
offset: 48 * index,
index,
})}
renderItem={({item}: {item: ItemData}) => <Item title={item.title} />}
keyExtractor={(item: ItemData) => item.id}
/>
);
}