mirror of
https://github.com/status-im/react-native.git
synced 2025-01-10 09:35:48 +00:00
db061ea8c7
Summary: A common UI pattern for list empty states is some text/images centered inside the visible part of the list. This is pretty hard to do currently because we wrap ListEmptyComponent with an extra view with no way to style it so we cannot just use `flex: 1` to make it fill the available space. - Added an example of ListEmptyComponent in the FlatList example in RNTester Before (no way to make ListEmptyComponent fill the space): <img width="377" alt="screen shot 2018-03-05 at 5 24 15 pm" src="https://user-images.githubusercontent.com/2677334/37003152-129db3ac-209a-11e8-9600-110f10d57144.png"> After: <img width="377" alt="screen shot 2018-03-05 at 5 09 20 pm" src="https://user-images.githubusercontent.com/2677334/37002809-e6971178-2098-11e8-8cf7-74bfb2f6a992.png"> - Tested some edge cases like returning null from the ListEmptyComponent - Tested in an app that uses FlatList + ListEmptyComponent [GENERAL] [MINOR] [VirtualizedList] - Don't wrap ListEmptyComponent in an extra view Closes https://github.com/facebook/react-native/pull/18206 Differential Revision: D7266274 Pulled By: sahrens fbshipit-source-id: 4636d2418474a4c86ac63e5e18a9afc391a518c5
230 lines
6.4 KiB
JavaScript
230 lines
6.4 KiB
JavaScript
/**
|
|
* Copyright (c) 2015-present, Facebook, Inc.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
* @flow
|
|
* @providesModule FlatListExample
|
|
*/
|
|
'use strict';
|
|
|
|
const Alert = require('Alert');
|
|
const React = require('react');
|
|
const ReactNative = require('react-native');
|
|
const {
|
|
Animated,
|
|
FlatList,
|
|
StyleSheet,
|
|
View,
|
|
} = ReactNative;
|
|
|
|
const RNTesterPage = require('./RNTesterPage');
|
|
|
|
const infoLog = require('infoLog');
|
|
|
|
const {
|
|
FooterComponent,
|
|
HeaderComponent,
|
|
ItemComponent,
|
|
ListEmptyComponent,
|
|
ItemSeparatorComponent,
|
|
PlainInput,
|
|
SeparatorComponent,
|
|
Spindicator,
|
|
genItemData,
|
|
getItemLayout,
|
|
pressItem,
|
|
renderSmallSwitchOption,
|
|
} = require('./ListExampleShared');
|
|
|
|
const AnimatedFlatList = Animated.createAnimatedComponent(FlatList);
|
|
|
|
const VIEWABILITY_CONFIG = {
|
|
minimumViewTime: 3000,
|
|
viewAreaCoveragePercentThreshold: 100,
|
|
waitForInteraction: true,
|
|
};
|
|
|
|
class FlatListExample extends React.PureComponent<{}, $FlowFixMeState> {
|
|
static title = '<FlatList>';
|
|
static description = 'Performant, scrollable list of data.';
|
|
|
|
state = {
|
|
data: genItemData(100),
|
|
debug: false,
|
|
horizontal: false,
|
|
inverted: false,
|
|
filterText: '',
|
|
fixedHeight: true,
|
|
logViewable: false,
|
|
virtualized: true,
|
|
empty: false,
|
|
};
|
|
|
|
_onChangeFilterText = (filterText) => {
|
|
this.setState({filterText});
|
|
};
|
|
|
|
_onChangeScrollToIndex = (text) => {
|
|
this._listRef.getNode().scrollToIndex({viewPosition: 0.5, index: Number(text)});
|
|
};
|
|
|
|
_scrollPos = new Animated.Value(0);
|
|
_scrollSinkX = Animated.event(
|
|
[{nativeEvent: { contentOffset: { x: this._scrollPos } }}],
|
|
{useNativeDriver: true},
|
|
);
|
|
_scrollSinkY = Animated.event(
|
|
[{nativeEvent: { contentOffset: { y: this._scrollPos } }}],
|
|
{useNativeDriver: true},
|
|
);
|
|
|
|
componentDidUpdate() {
|
|
this._listRef.getNode().recordInteraction(); // e.g. flipping logViewable switch
|
|
}
|
|
|
|
render() {
|
|
const filterRegex = new RegExp(String(this.state.filterText), 'i');
|
|
const filter = (item) => (
|
|
filterRegex.test(item.text) || filterRegex.test(item.title)
|
|
);
|
|
const filteredData = this.state.data.filter(filter);
|
|
return (
|
|
<RNTesterPage
|
|
noSpacer={true}
|
|
noScroll={true}>
|
|
<View style={styles.container}>
|
|
<View style={styles.searchRow}>
|
|
<View style={styles.options}>
|
|
<PlainInput
|
|
onChangeText={this._onChangeFilterText}
|
|
placeholder="Search..."
|
|
value={this.state.filterText}
|
|
/>
|
|
<PlainInput
|
|
onChangeText={this._onChangeScrollToIndex}
|
|
placeholder="scrollToIndex..."
|
|
/>
|
|
</View>
|
|
<View style={styles.options}>
|
|
{renderSmallSwitchOption(this, 'virtualized')}
|
|
{renderSmallSwitchOption(this, 'horizontal')}
|
|
{renderSmallSwitchOption(this, 'fixedHeight')}
|
|
{renderSmallSwitchOption(this, 'log')}
|
|
{renderSmallSwitchOption(this, 'inverted')}
|
|
{renderSmallSwitchOption(this, 'empty')}
|
|
{renderSmallSwitchOption(this, 'debug')}
|
|
<Spindicator value={this._scrollPos} />
|
|
</View>
|
|
</View>
|
|
<SeparatorComponent />
|
|
<AnimatedFlatList
|
|
ItemSeparatorComponent={ItemSeparatorComponent}
|
|
ListHeaderComponent={<HeaderComponent />}
|
|
ListFooterComponent={FooterComponent}
|
|
ListEmptyComponent={ListEmptyComponent}
|
|
data={this.state.empty ? [] : filteredData}
|
|
debug={this.state.debug}
|
|
disableVirtualization={!this.state.virtualized}
|
|
getItemLayout={this.state.fixedHeight ?
|
|
this._getItemLayout :
|
|
undefined
|
|
}
|
|
horizontal={this.state.horizontal}
|
|
inverted={this.state.inverted}
|
|
key={(this.state.horizontal ? 'h' : 'v') +
|
|
(this.state.fixedHeight ? 'f' : 'd')
|
|
}
|
|
keyboardShouldPersistTaps="always"
|
|
keyboardDismissMode="on-drag"
|
|
legacyImplementation={false}
|
|
numColumns={1}
|
|
onEndReached={this._onEndReached}
|
|
onRefresh={this._onRefresh}
|
|
onScroll={this.state.horizontal ? this._scrollSinkX : this._scrollSinkY}
|
|
onViewableItemsChanged={this._onViewableItemsChanged}
|
|
ref={this._captureRef}
|
|
refreshing={false}
|
|
renderItem={this._renderItemComponent}
|
|
contentContainerStyle={styles.list}
|
|
viewabilityConfig={VIEWABILITY_CONFIG}
|
|
/>
|
|
</View>
|
|
</RNTesterPage>
|
|
);
|
|
}
|
|
_captureRef = (ref) => { this._listRef = ref; };
|
|
_getItemLayout = (data: any, index: number) => {
|
|
return getItemLayout(data, index, this.state.horizontal);
|
|
};
|
|
_onEndReached = () => {
|
|
if (this.state.data.length >= 1000) {
|
|
return;
|
|
}
|
|
this.setState((state) => ({
|
|
data: state.data.concat(genItemData(100, state.data.length)),
|
|
}));
|
|
};
|
|
_onRefresh = () => Alert.alert('onRefresh: nothing to refresh :P');
|
|
_renderItemComponent = ({item, separators}) => {
|
|
return (
|
|
<ItemComponent
|
|
item={item}
|
|
horizontal={this.state.horizontal}
|
|
fixedHeight={this.state.fixedHeight}
|
|
onPress={this._pressItem}
|
|
onShowUnderlay={separators.highlight}
|
|
onHideUnderlay={separators.unhighlight}
|
|
/>
|
|
);
|
|
};
|
|
// 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) {
|
|
infoLog(
|
|
'onViewableItemsChanged: ',
|
|
info.changed.map((v) => ({...v, item: '...'})),
|
|
);
|
|
}
|
|
};
|
|
_pressItem = (key: string) => {
|
|
this._listRef.getNode().recordInteraction();
|
|
pressItem(this, key);
|
|
};
|
|
_listRef: AnimatedFlatList;
|
|
}
|
|
|
|
|
|
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,
|
|
},
|
|
});
|
|
|
|
module.exports = FlatListExample;
|