Files
react-native/packages/rn-tester/js/examples/FlatList/FlatList-basic.js
Sam Zhou cf664c65e2 Standardize subtyping error code into incompatible-type in react native and metro (#53312)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53312

Changelog: [Internal]

Reviewed By: jbrown215

Differential Revision: D80400976

fbshipit-source-id: 196af69c0b9621b2a2675b232406639773e04933
2025-08-18 09:04:31 -07:00

453 lines
13 KiB
JavaScript

/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
* @format
*/
'use strict';
import type {Item} from '../../components/ListExampleShared';
import type {RNTesterModuleExample} from '../../types/RNTesterTypes';
import type {FlatList, ListRenderItemInfo} from 'react-native';
import {
FooterComponent,
HeaderComponent,
ItemComponent,
ItemSeparatorComponent,
ListEmptyComponent,
LoadingComponent,
PlainInput,
SeparatorComponent,
Spindicator,
genNewerItems,
genOlderItems,
getItemLayout,
pressItem,
renderSmallSwitchOption,
} from '../../components/ListExampleShared';
import RNTesterPage from '../../components/RNTesterPage';
import * as React from 'react';
import {
Alert,
Animated,
I18nManager,
Platform,
StyleSheet,
TextInput,
View,
} from 'react-native';
const PAGE_SIZE = 100;
const NUM_PAGES = 10;
const INITIAL_PAGE_OFFSET = Math.floor(NUM_PAGES / 2);
const LOAD_TIME = 2000;
const VIEWABILITY_CONFIG = {
minimumViewTime: 3000,
viewAreaCoveragePercentThreshold: 100,
waitForInteraction: true,
};
type Props = $ReadOnly<{}>;
type State = {
data: Array<Item>,
first: number,
last: number,
debug: boolean,
horizontal: boolean,
inverted: boolean,
filterText: string,
fixedHeight: boolean,
logViewable: boolean,
virtualized: boolean,
empty: boolean,
useFlatListItemComponent: boolean,
fadingEdgeLength: number,
onPressDisabled: boolean,
textSelectable: boolean,
isRTL: boolean,
maintainVisibleContentPosition: boolean,
previousLoading: boolean,
nextLoading: boolean,
};
const IS_RTL = I18nManager.isRTL;
class FlatListExample extends React.PureComponent<Props, State> {
state: State = {
data: genNewerItems(PAGE_SIZE, PAGE_SIZE * INITIAL_PAGE_OFFSET),
first: PAGE_SIZE * INITIAL_PAGE_OFFSET,
last: PAGE_SIZE + PAGE_SIZE * INITIAL_PAGE_OFFSET,
debug: false,
horizontal: false,
inverted: false,
filterText: '',
fixedHeight: true,
logViewable: false,
virtualized: true,
empty: false,
useFlatListItemComponent: false,
fadingEdgeLength: 0,
onPressDisabled: false,
textSelectable: true,
isRTL: IS_RTL,
maintainVisibleContentPosition: true,
previousLoading: false,
nextLoading: false,
};
/* $FlowFixMe[missing-local-annot] The type annotation(s) required by Flow's
* LTI update could not be added via codemod */
_onChangeFilterText = filterText => {
this.setState({filterText});
};
_onChangeScrollToIndex = (text: mixed) => {
this._listRef?.scrollToIndex({viewPosition: 0.5, index: Number(text)});
};
// $FlowFixMe[missing-local-annot]
_scrollPos = new Animated.Value(0);
// $FlowFixMe[missing-local-annot]
_scrollSinkX = Animated.event(
[{nativeEvent: {contentOffset: {x: this._scrollPos}}}],
{useNativeDriver: true},
);
// $FlowFixMe[missing-local-annot]
_scrollSinkY = Animated.event(
[{nativeEvent: {contentOffset: {y: this._scrollPos}}}],
{useNativeDriver: true},
);
componentDidUpdate() {
this._listRef?.recordInteraction(); // e.g. flipping logViewable switch
}
_setBooleanValue: string => boolean => void = key => value =>
// $FlowFixMe[incompatible-type]
this.setState({[key]: value});
_setIsRTL: boolean => void = value => {
I18nManager.forceRTL(value);
this.setState({isRTL: value});
Alert.alert(
'Reload this page',
'Please reload this page to change the UI direction! ' +
'All examples in this app will be affected. ' +
'Check them out to see what they look like in RTL layout.',
);
};
render(): React.Node {
const filterRegex = new RegExp(String(this.state.filterText), 'i');
const filter = (item: Item) =>
filterRegex.test(item.text) || filterRegex.test(item.title);
const filteredData = this.state.data.filter(filter);
const flatListItemRendererProps = this._renderItemComponent();
return (
<RNTesterPage noScroll={true} title="Simple list of items">
<View style={styles.container}>
<View style={styles.searchRow}>
<View style={styles.options}>
<PlainInput
testID="search_bar_flat_list"
onChangeText={this._onChangeFilterText}
placeholder="Search..."
value={this.state.filterText}
/>
<PlainInput
onChangeText={this._onChangeScrollToIndex}
placeholder="scrollToIndex..."
/>
</View>
<View style={styles.options}>
{renderSmallSwitchOption(
'Virtualized',
this.state.virtualized,
this._setBooleanValue('virtualized'),
)}
{renderSmallSwitchOption(
'Horizontal',
this.state.horizontal,
this._setBooleanValue('horizontal'),
)}
{renderSmallSwitchOption(
'Fixed Height',
this.state.fixedHeight,
this._setBooleanValue('fixedHeight'),
)}
{renderSmallSwitchOption(
'Log Viewable',
this.state.logViewable,
this._setBooleanValue('logViewable'),
)}
{renderSmallSwitchOption(
'Inverted',
this.state.inverted,
this._setBooleanValue('inverted'),
)}
{renderSmallSwitchOption(
'Empty',
this.state.empty,
this._setBooleanValue('empty'),
'switch_empty_option',
)}
{renderSmallSwitchOption(
'Debug',
this.state.debug,
this._setBooleanValue('debug'),
)}
{renderSmallSwitchOption(
'onPress Disabled',
this.state.onPressDisabled,
this._setBooleanValue('onPressDisabled'),
)}
{renderSmallSwitchOption(
'Text Selectable',
this.state.textSelectable,
this._setBooleanValue('textSelectable'),
)}
{renderSmallSwitchOption(
'Use FlatListItemComponent',
this.state.useFlatListItemComponent,
this._setBooleanValue('useFlatListItemComponent'),
)}
{renderSmallSwitchOption(
'Is RTL',
this.state.isRTL,
this._setIsRTL,
)}
{renderSmallSwitchOption(
'Maintain content position',
this.state.maintainVisibleContentPosition,
this._setBooleanValue('maintainVisibleContentPosition'),
)}
{Platform.OS === 'android' && (
<View>
<TextInput
placeholder="Fading edge length"
underlineColorAndroid="black"
keyboardType={'numeric'}
onChange={event =>
this.setState({
fadingEdgeLength: Number(event.nativeEvent.text),
})
}
/>
</View>
)}
<Spindicator value={this._scrollPos} />
</View>
</View>
<SeparatorComponent />
<Animated.FlatList
fadingEdgeLength={this.state.fadingEdgeLength}
ItemSeparatorComponent={
this.state.horizontal ? null : ItemSeparatorComponent
}
ListHeaderComponent={
this.state.previousLoading ? LoadingComponent : HeaderComponent
}
ListFooterComponent={
this.state.nextLoading ? LoadingComponent : FooterComponent
}
ListEmptyComponent={ListEmptyComponent}
// $FlowFixMe[missing-empty-array-annot]
data={this.state.empty ? [] : filteredData}
debug={this.state.debug}
disableVirtualization={!this.state.virtualized}
getItemLayout={
this.state.fixedHeight ? this._getItemLayout : undefined
}
accessibilityRole="list"
horizontal={this.state.horizontal}
inverted={this.state.inverted}
key={
(this.state.horizontal ? 'h' : 'v') +
(this.state.fixedHeight ? 'f' : 'd')
}
keyboardShouldPersistTaps="always"
keyboardDismissMode="on-drag"
numColumns={1}
onStartReached={this._onStartReached}
initialScrollIndex={Math.floor(PAGE_SIZE / 2)}
onEndReached={this._onEndReached}
onRefresh={this._onRefresh}
onScroll={
this.state.horizontal ? this._scrollSinkX : this._scrollSinkY
}
onScrollToIndexFailed={this._onScrollToIndexFailed}
onViewableItemsChanged={this._onViewableItemsChanged}
ref={this._captureRef}
refreshing={false}
contentContainerStyle={styles.list}
viewabilityConfig={VIEWABILITY_CONFIG}
maintainVisibleContentPosition={
this.state.maintainVisibleContentPosition
? {minIndexForVisible: 0}
: undefined
}
{...flatListItemRendererProps}
/>
</View>
</RNTesterPage>
);
}
_captureRef = (ref: FlatList<mixed> | null) => {
this._listRef = ref;
};
// $FlowFixMe[missing-local-annot]
_getItemLayout = (data: any, index: number) => {
return getItemLayout(data, index, this.state.horizontal);
};
_onStartReached = () => {
if (
this.state.empty ||
this.state.first <= 0 ||
this.state.previousLoading
) {
return;
}
this.setState({previousLoading: true});
setTimeout(() => {
this.setState(state => ({
previousLoading: false,
data: genOlderItems(PAGE_SIZE, state.first).concat(state.data),
first: state.first - PAGE_SIZE,
}));
}, LOAD_TIME);
};
_onEndReached = () => {
if (
this.state.empty ||
this.state.last >= PAGE_SIZE * NUM_PAGES ||
this.state.nextLoading
) {
return;
}
this.setState({nextLoading: true});
setTimeout(() => {
this.setState(state => ({
nextLoading: false,
data: state.data.concat(genNewerItems(PAGE_SIZE, state.last)),
last: state.last + PAGE_SIZE,
}));
}, LOAD_TIME);
};
// $FlowFixMe[missing-local-annot]
_onPressCallback = () => {
const {onPressDisabled} = this.state;
const warning = () => console.log('onPress disabled');
const onPressAction = onPressDisabled ? warning : this._pressItem;
return onPressAction;
};
// $FlowFixMe[missing-local-annot]
_onRefresh = () => Alert.alert('onRefresh: nothing to refresh :P');
// $FlowFixMe[missing-local-annot]
_renderItemComponent = () => {
const renderProp = ({item, separators}: ListRenderItemInfo<Item>) => {
return (
<ItemComponent
testID={`item_${item.key}`}
item={item}
horizontal={this.state.horizontal}
fixedHeight={this.state.fixedHeight}
onPress={this._onPressCallback()}
onShowUnderlay={separators.highlight}
onHideUnderlay={separators.unhighlight}
textSelectable={this.state.textSelectable}
/>
);
};
return this.state.useFlatListItemComponent
? {
renderItem: undefined,
ListItemComponent: renderProp,
}
: {renderItem: renderProp};
};
_onScrollToIndexFailed = ({
index,
highestMeasuredFrameIndex,
}: {
index: number,
highestMeasuredFrameIndex: number,
}) => {
console.warn(
`failed to scroll to index: ${index} (measured up to ${highestMeasuredFrameIndex})`,
);
};
// This is called when items change viewability by scrolling into or out of
// the viewable area.
_onViewableItemsChanged = (info: {
changed: Array<{
key: string,
isViewable: boolean,
item: any,
index: ?number,
section?: any,
...
}>,
...
}) => {
// Impressions can be logged here
if (this.state.logViewable) {
console.log(
'onViewableItemsChanged: ',
info.changed.map(v => ({...v, item: '...'})),
);
}
};
_pressItem = (key: string) => {
this._listRef?.recordInteraction();
const index = this.state.data.findIndex(item => item.key === key);
const itemState = pressItem(this.state.data[index]);
this.setState(state => ({
...state,
data: [
...state.data.slice(0, index),
itemState,
...state.data.slice(index + 1),
],
}));
};
_listRef: FlatList<mixed> | null;
}
const styles = StyleSheet.create({
container: {
backgroundColor: 'rgb(239, 239, 244)',
flex: 1,
},
list: {
backgroundColor: 'white',
flexGrow: 1,
},
options: {
flexDirection: 'row',
flexWrap: 'wrap',
alignItems: 'center',
},
searchRow: {
paddingHorizontal: 10,
},
});
export default ({
title: 'Basic',
name: 'basic',
description: 'Simple list of items',
render: () => <FlatListExample />,
}: RNTesterModuleExample);