React-Native FlatList render a zero state when data is empty - react-native

Is it possible to render an alternate component when the data is empty? The only reason I would not just render the list or not render the list is that the ListHeaderComponent is necessary in both scenarios (data.length and !data.length)...
const data = []
<FlatList
contentContainerStyle={styles.list}
data={data} // if empty or !data.length render <ZeroComponent/>

UPDATE
react-native recently added ListEmptyComponent
const data = []
_listEmptyComponent = () => {
return (
<View>
// any activity indicator or error component
</View>
)
}
<FlatList
data={data}
ListEmptyComponent={this._listEmptyComponent}
contentContainerStyle={styles.list}
/>
const data = []
renderFooter = () => {
if (data.length != 0) return null;
return (
<View>
// any activity indicator or error component
</View>
);
};
<FlatList
contentContainerStyle={styles.list}
data={data}
ListFooterComponent={this.renderFooter}
/>
Hope this helps

There is a prop ListEmptyComponent which can do the job.
<FlatList
data={this.state.data}
renderItem={this.renderItem}
keyExtractor={(item, index) => item.id}
ListHeaderComponent={this.showFilters()}
ListEmptyComponent={this.showEmptyListView()}
/>

You can do something like this
{data.length > 0 ? <FlatList ... /> : <EmptyList />}
Hope it helps

Related

react-native-snap-carousel when swiping it renders all items each time

I'm using react-native-snap-carousel and I have large data, and it feel quite laggy when swiping each time.
I debugged the renderItem method and it's rendering each time on swipe. Should this happen normally or what ?
Is there a way to solve this issue ?
const [activeSlide, setActiveSlide] = useState(0);
const renderItem = useCallback(
({ item, index }) => {
return (
<CarouselImage
ad={ad}
item={item}
index={index}
showImage={showImage}
/>
);
},
[ad, showImage]);
return ad?.videos?.length > 0 || ad?.images?.length > 0 ? (
<View style={styles.container}>
<Carousel
initialNumToRender={selectedItems.length}
maxToRenderPerBatch={5}
ref={carouselRef}
swipeThreshold={5}
itemWidth={wp(375)}
data={selectedItems}
sliderWidth={wp(375)}
enableMomentum={false}
lockScrollWhileSnapping
renderItem={renderItem}
onSnapToItem={(index) => setActiveSlide(index)}
/>
<Pagination
activeOpacity={1}
tappableDots={true}
animatedDuration={100}
inactiveDotScale={0.4}
inactiveDotOpacity={0.4}
carouselRef={carouselRef}
dotStyle={styles.dotStyle}
activeDotIndex={activeSlide}
dotsLength={selectedItems.length}
containerStyle={styles.pagination}
dotContainerStyle={styles.dotContainer}
inactiveDotStyle={styles.inactiveDotStyle}
/>
</View>

FlatList search bar does not persist keyboard React Native

I'm fetching data from an API and implementing search in a FlatList but the keyboard dismisses automatically after every key-press.
I'm refering this article but implementing it in a Functional Component.
const renderHeader = () => {
return <SearchBar
placeholder="Type Here..."
lightTheme
round
onChangeText={text => searchFilterFunction(text)}
value={value}
autoCorrect={false} />;
}
const searchFilterFunction = (text) => {
setValue(text);
const newData = APIData.filter(item => {
const itemData = `${item.name.toUpperCase()}`;
const textData = text.toUpperCase();
return itemData.includes(textData);
});
setData(newData);
}
return (
<FlatList
keyExtractor={(item) => item._id}
data={data}
ItemSeparatorComponent={renderSeparator}
ListHeaderComponent={renderHeader}
ListFooterComponent={renderFooter}
onRefresh={handleRefresh}
refreshing={refreshing}
renderItem={({ item }) => (
<Card>
<Card.Content style={{ flexDirection: "row" }}>
<Text>{"Name: " + item.name}</Text>
<Text>{"Status: " + (item.isaccepted ? "Accepted" : "Pending")}</Text>
<Text>{"ID: " + item.id}</Text>
</Card.Content>
</Card>
)} />
)
Thanks in advance.
I was doing same thing, adding search bar as a header to FlatList. Unfortunately, this also updates the header when you update the flatlist data when search filtering is complete and hence focusing out of SearchBar. At the end, due to time constraints, I ended up putting SearchBar at the top of FlatList.
Try rendering your ListHeaderComponent as JSX element directly, instead of using callback
<FlatList
ListHeaderComponent={
<View>
<Text>I am the header</Text>
</View>
}
...props
/>

Difference between FlatList and VirtualizedList

I am new in react native, and am confused about the difference between FlatList and VirtualizedList.
So,
What are the differences between FlatList and VirtualizedList ?
When should I use each ?
The <FlatList> is a performant interface for rendering basic, flat lists.
On the other side, the <VirtualizedList> is a base implementation of the <FlatList> and <SectionList> components, which are also better documented. In general, <VirtualizedList> should only really be used if you need more flexibility than FlatList provides, e.g. for use with immutable data instead of plain arrays.
FlatList example:
const App = () => {
const renderItem = ({ item }) => (
<Item title={item.title} />
);
return (
<SafeAreaView style={styles.container}>
<FlatList
data={DATA}
renderItem={renderItem}
keyExtractor={item => item.id}
/>
</SafeAreaView>
);
}
VirtualizedList example:
const App = () => {
return (
<SafeAreaView style={styles.container}>
<VirtualizedList
data={DATA}
initialNumToRender={4}
renderItem={({ item }) => <Item title={item.title} />}
keyExtractor={item => item.key}
getItemCount={getItemCount}
getItem={getItem}
/>
</SafeAreaView>
);
}
More info:
https://reactnative.dev/docs/virtualizedlist
https://reactnative.dev/docs/flatlist

React Native: Conditional props for List

I was wondering if there is a way to set a group of properties for a list component based on a condition.
render() {
return (
<SectionList
ref={(ref) => {this.listRef = ref}}
style={styles.listContainer}
sections={this.props.listData}
fetchData={this.props.fetchData}
keyExtractor={this.keyExtractor}
renderSectionHeader={this.renderSectionHeader}
renderItem={this.renderItem}
ItemSeparatorComponent={this.separator}
keyboardDismissMode='on-drag'
initialNumToRender={6}
stickySectionHeadersEnabled={false}
// ListFooterComponent={this.renderFooter}
// onEndReachedThreshold={0.5}
// onEndReached={() => {
// this.props.fetchMoreData()}}
/>
)
}
I would like ListFooterComponent, onEndReachedThreshold and onEndReached only to be set if this.props.fetchMoreData is set. Do I need to put an if-statement at the top of the render function or is it possible to create a separate prop to group those 3 and use the spread operator to put this back into the SectionList based on the condition. I am not quite sure how to do this.
I ended up with the following
render() {
optional = {}
if (this.props.fetchMoreData) {
optional.onEndReachedThreshold = 0.5
optional.onEndReached = () => {
this.props.fetchMoreData()}
optional.ListFooterComponent = this.renderFooter
}
return (
<SectionList
ref={(ref) => {this.listRef = ref}}
style={styles.listContainer}
sections={this.props.listData}
fetchData={this.props.fetchData}
keyExtractor={this.keyExtractor}
renderSectionHeader={this.renderSectionHeader}
renderItem={this.renderItem}
ItemSeparatorComponent={this.separator}
keyboardDismissMode='on-drag'
initialNumToRender={6}
stickySectionHeadersEnabled={false}
{...optional}
/>
)
I think I initially tripped over optional.ListFooterComponent. It just looked strange to me so I assumed it must be wrong ;)

ScrollToEnd after update data for Flatlist

I'm making a chat box with Flatlist. I want to add a new item to data then scroll to bottom of list. I use scrollToEnd method but it did not work. How can I do this?
<FlatList
ref="flatList"
data={this.state.data}
extraData = {this.state}
renderItem={({item}) => <Text style={styles.chatFlatListItem}>{item.chat}</Text>}
/>
AddChat(_chat){
var arr = this.state.data;
arr.push({key: arr.length, chat: _chat});
var _data = {};
_data["data"] = arr;
this.setState(_data);
this.refs.flatList.scrollToEnd();
}
I found a better solution,scrollToEnd() is not working because it is triggered before the change is made to the FlatList.
Since it inherits from ScrollView the best way here is to call scrollToEnd() in onContentSizeChange like so :
<FlatList
ref = "flatList"
onContentSizeChange={()=> this.refs.flatList.scrollToEnd()} />
Thanks #Kernael, just add a timeout like so:
setTimeout(() => this.refs.flatList.scrollToEnd(), 200)
const flatList = React.useRef(null)
<FlatList
ref={flatList}
onContentSizeChange={() => {
flatList.current.scrollToEnd();
}}
data={this.state.data}
extraData = {this.state}
renderItem={({item}) => <Text style={styles.chatFlatListItem}>{item.chat}</Text>}
/>
try this,it works.
My issue here was that scrollToEnd() worked fine on mobile but on web it always scrolled to the top. Probably because I have elements with different size in the FlatList and couldn't define getItemLayout. But thanks to the accepted answer here I solved it. Just with different approach.
const ref = React.useRef<FlatList>();
function handleScrollToEnd(width, height) {
if (ref.current) {
ref.current.scrollToOffset({offset: height});
}
}
<FlatList
ref={ref}
onContentSizeChange={handleScrollToEnd}
/>
This works great on both the mobile and web. Hope it helps to somebody.
Change your code as below. The ref is modified and It's better to use getItemLayout in your FlatList according to this.
AddChat(_chat){
var arr = this.state.data;
arr.push({key: arr.length, chat: _chat});
var _data = {};
_data["data"] = arr;
this.setState(_data);
this.flatList.scrollToEnd();
}
<FlatList
ref={elm => this.flatList = elm}
data={this.state.data}
extraData = {this.state}
renderItem={({item}) => <Text style={styles.chatFlatListItem}>{item.chat}</Text>}
getItemLayout={(data, index) => (
{length: ITEM_HEIGHT, offset: ITEM_HEIGHT * index, index}
)}
/>
Note: Replace the ITEM_HEIGHT with the real value of height of your list items.
Try to use inverted prop on Fatlist itself
Pass your data like this [...data].reverse()
If you are at the middle of list and you need to scroll to end when a new item is added, just use:
ref => flatlistRef.current?.scrollToOffset({offset:0})
seems caused by this line
https://github.com/facebook/react-native/blob/3da3d82320bd035c6bd361a82ea12a70dba4e851/Libraries/Lists/VirtualizedList.js#L1573
when use trigger scrollToEnd, frame.offset is 0
https://github.com/facebook/react-native/blob/3da3d82320bd035c6bd361a82ea12a70dba4e851/Libraries/Lists/VirtualizedList.js#L390
if you wait 1 second, _onContentSize changes and frame.offset is valorized (for ex. 1200 px).
Related post https://github.com/facebook/react-native/issues/30373#issuecomment-1176199466
Simply add a loader before Flatlist renders. For example:
const flatListRef = useRef(null);
const [messages, setMessages] = useState([]);
if(!messages.length){
return <Loader />
}
return (
<View style={styles.messagesContainer}>
<FlatList
ref={flatListRef}
data={messages}
onContentSizeChange={() => {
if (flatListRef.current) {
flatListRef?.current?.scrollToEnd();
}
}}
renderItem={({item, index}) => {
return (
<DisplayMessages
message={item}
index={index}
/>
);
}}
/>
</View>