React-native - Pagination in horizontal FlatList with separators - react-native

How to adjust horizontal FlatList with separators in order to skip separators when pagination is enabled. I want to see separators only when swiping between items. I tried to set it in getItemLayout but it doesn't work properly. I used getItemLayout = (_, index) => ({ length: window.width, offset: (window.width + separatorWidth) * index, index }) Behaviour looks like that

This confused me as well.
There are a few snap related properties inherited/extended from <ScrollView> that are useful here.
Checkout: snapToInterval and snapToOffsets.
If you're using a <FlatList> or <ScrollView> to act as a horizontal full-width carousel, and want to enforce snapping so that a single "page" within the list is always within view (i.e. users can't stop partially between views), these snap props are what you need.
Note: you need to disable pagingEnabled in order for these props to be respected.
Simplified example code:
render() {
const totalItemWidth = window.width + separatorWidth;
return (
<FlatList
{ /* ... other props — data, renderItem, style, etc ... */}
horizontal
showsHorizontalScrollIndicator={false}
snapToInterval={totalItemWidth}
decelerationRate="fast"
bounces={false}
getItemLayout={(data, index) => ({
length: totalItemWidth,
offset: totalItemWidth * index,
index,
})}
ItemSeparatorComponent={SomeSeparatorComponent}
/>
)
}
How it behaves on iOS verse Android:
Android is a bit clunkier and I'm still refining the decelerationRate and overall feel... but it's close, IMO.

Related

Is there a FlatList-like View without the scrolling?

Basically, I have a list of items (with images) inside a single Card (custom component). Because the rendering of those items is slow, I wanted to use a FlatList to render them incrementally.
Unfortunately, I get the expected error
VirtualizedLists should never be nested inside plain ScrollViews ...
But I don't actually want to use a ScrollView inside the Card. I just want to render a few Items in a single Card, which should change its size to fit all the items.
Setting scrollEnabled={false} on the FlatList still shows the error above.
Using the ListHeaderComponent and ListFooterComponent props is not an option, because the content above and below should NOT be rendered inside the Card.
Here is a minimal example of what I mean:
const Comp = () => {
return (
<ScrollView contentInsetAdjustmentBehavior="automatic">
<Text>Header</Text>
<Card>
<FlatList
data={data}
renderItem={({ item }) => (
<Image source={{uri: item.localImageUrl}}/>
)}
keyExtractor={(item) => item.id}
scrollEnabled={false}
initialNumToRender={0}
maxToRenderPerBatch={3}
contentInsetAdjustmentBehavior='automatic'
/>
</Card>
<Text>Footer</Text>
</ScrollView>
);
};
What's interesting though, that aside from the error - I get the result I expected, and I could technically hide that error and ignore it, but that does not seem like the recommended approach.
Important: I am not specifically looking for a FlatList solution. It could technically be any Component that renders items incrementally in a non-blocking way.
The important point with a Flatlist is the reusing of cells so that not all components need to be rendered at the same time. So scrolling is an important part of this. On the other hand two scrollable components inside eachother will make it impossible for the system to know which component should be scrolled.
If there are only 3 items and it should not be scrollable you can just include a list of items inside the Scrollview like this:
const Comp = () => {
return (
<ScrollView contentInsetAdjustmentBehavior="automatic">
<Text>Header</Text>
<Card>
{ data.map((item, index) => {
return (
<Text key={index}>{item.title}</Text>
);
}) }
</Card>
<Text>Footer</Text>
</ScrollView>
);
};

React Native SectionList scrollToLocation not working inside a scrollview

I'm trying to have a normal info view and a sectionList inside a scrollView. Though the view gets scrolled, when I try to use scrollToLocation for scrolling to specific index selected it is not scrolling. Also I have tried using other props onMomentumScrollEnd in sectionList which is also not working. When I remove scrollView it works perfectly.
<ScrollView>
<View style={{ height: 300, backgroundColor: 'rgba(0,0,0,0.2)' }} />
<View>
<SectionList
ref={(ref) => (this.contentRef = ref)}
stickySectionHeadersEnabled={false}
showsVerticalScrollIndicator={false}
sections={sectionListData}
keyExtractor={(item) => item.id}
onMomentumScrollEnd={() => {
this.setState({ onScrollFinished: true });
this.setViewableItem();
}}
onScrollEndDrag={() => {
this.setViewableItem();
}}
onViewableItemsChanged={this.onViewableItemsChanged}
renderItem={this.renderSectionItem}
renderSectionHeader={!this.props.isMenuLoading && this.renderSectionHeader}
initialNumToRender={500}
onScrollToIndexFailed={(info) => console.log('info', info)}
/>
</View>
</ScrollView>
setActiveIndex(key) {
this.setState({ activeIndex: key, updatedAt: Date.now() });
if (isValidElement(this.headerRef)) {
this.headerRef.scrollToIndex({ index: key, animated: true, viewPosition: 0.5 });
}
if (isValidElement(this.contentRef)) {
this.contentRef.scrollToLocation({
sectionIndex: key,
itemIndex: 0,
animated: false,
viewPosition: 0
});
}
}
It appears this is a long-standing issue which no one seems to have an answer for. Issue #25295 on the react-native repository from June 2019 reported this behavior and was automatically closed as stale after 3 months. Issue #31136 reports the same problem, and there are presumably other references to it as well.
The behavior appears to be related to the underlying implementation of the scroll* imperative functions. Specifically, the call stack looks something like this:
scrollToIndex in VirtualizedList
scrollToLocation in VirtualizedSectionList
scrollToLocation in SectionList
The bit that fails is when VirtualizedList attempts to call this._scrollRef.scrollTo, which is apparently not defined when the SectionList is nested in a ScrollView or other scrollable component like FlatList.
My suggestion would be to refactor the layout such that there are not scrollable items nested under other scrollable items on the same axis.

scrollToIndex is not working in RTL FlatList React-Native

I have Horizontal FlatList & now I changed phone language to Arabic (RTL). scrollToIndex() is not working in RTL, without RTL it's working fine as expected. Any suggestions?
FlatList
<FlatList
style={myStyles.flatListStyle}
horizontal={true}
data={this.props.dataForFlatList}
renderItem={this.renderItem}
ref={(ref) => { this.flatListRef = ref; }}
keyExtractor={item => item.id.toString()} />
renderItem function on Press() am passing item id
this.flatListRef.scrollToIndex({ animated: true, index: id }); // Working fine without RTL - NOT WORKING WITH RTL
Thanks in advance.
Found this solution.
flatListRef.current.scrollToIndex({
index: I18nManager.isRTL && Platform.OS === "ios" ? list.length - 1 - index : index,
});
For some reason ios scrolls to the opposite value in RTL localization.
That is, when choosing the latter to the first. When choosing the penultimate to the second and so on.
If you are scrolling outside of the render window you need to specifying the getItemLayout props

React Native - getItemLayout setting for multiple columns

I want to implement a scrollToIndex function for my flatlist. However, it prompts and out of range error. I am wondering is it related to getItemLayout inside flatlist if it is single column only.
getItemLayout = (data, index) => (
{ length: win.width*0.335, offset: (win.width*0.335)*index, index }
)
render(){
return(
<FlatList
numColumns={3}
ref={(ref) => { this.dataList = ref; }}
renderItem={({ item, index }) => this._renderItem(item, index)}
data={this.state.data}
extraData={this.state}
getItemLayout={this.getItemLayout}//required for scrollToIndex
style={{ flex: 1, paddingBottom: win.height * 0.02 }}
keyExtractor={(item, index) => index}
/>
)
}
React Native FlatList getItemLayout for multiple columns
The idea is the FlatList just needs to know for this index, how tall is it (length), how do I jump to it (offset) and pass back the current index. Not sure why they need the index as that doesn't change.
const FIXED_ITEM_HEIGHT = 100
const NUM_COLUMNS = 3
getItemLayout = (data, index) => ({
length: FIXED_ITEM_HEIGHT,
offset: FIXED_ITEM_HEIGHT * Math.floor(index / NUM_COLUMNS),
index
})
HOWEVER... This feature doesn't work as it's supposed to. It fires on scroll but the index doesn't change. There are many known issues at the time of this writing so don't use it in production.
https://github.com/facebook/react-native/issues/20467
So currently Facebook gets an unlike on this feature.
When you set numColumns above 1 ScrollView will call getItemLayout once per row instead of per item. So it's better to think of the index as "rowIndex" rather than item index.
I discovered this when my ScrollView got jumpy after loading around 17 rows of content or 51 items, and it would at certain scroll points jump 1 row up/down.

React native flatlist initial scroll to bottom

I am trying to create a chat in React native using a <Flatlist />
Like WhatsApp and other chat apps, the messages start at the bottom.
After fetching the messages from my API, I call
this.myFlatList.scrollToEnd({animated: false});
But it scrolls somewhere in the middle and sometimes with fewer items to the bottom and sometimes it does nothing.
How can I scroll initially to the bottom?
My chat messages have different heights, so I can't calculate the height.
I had similar issue. If you want to have you chat messages start at the bottom, you could set "inverted" to true and display your messages and time tag in an opposite direction.
Check here for "inverted" property for FlatList. https://facebook.github.io/react-native/docs/flatlist#inverted
If you want to have you chat messages start at the top, which is what I am trying to achieve. I could not find a solution in FlatList, because as you said, the heights are different, I could not use getItemLayout which make "scrollToEnd" behave in a strange way.
I follow the approach that #My Mai mentioned, using ScrollView instead and do scrollToEnd({animated: false}) in a setTimeout function. Besides, I added a state to hide the content until scrollToEnd is done, so user would not be seeing any scrolling.
I solved this issue with inverted property and reverse function
https://facebook.github.io/react-native/docs/flatlist#inverted
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reverse
<FlatList
inverted
data={[...data].reverse()}
renderItem={renderItem}
keyExtractor={(item) => item.id}
/>
You can use this solution in chat component.
I faced the same issue with you and then I moved to use ScrollView.
It is fixed:
componentDidMount() {
setTimeout(() => {
this.scrollView.scrollToEnd();
});
}
<ScrollView ref={(ref) => { this.scrollView = ref; }} style={styles.messages}>
{
messages.map((item, i) => (
<Message
key={i}
direction={item.userType === 'banker' ? 'right' : 'left'}
text={item.message}
name={item.name}
time={item.createdAt}
/>
))
}
</ScrollView>`
Set initialScrollIndex to your data set's length - 1.
I.e.
<Flatlist
data={dataSet}
initialScrollIndex={dataSet.length - 1}
/>
There are two types of 'good' solutions as of 2021.
First one is with timeout, references and useEffect. Here's the full example using Functional Components and Typescript:
// Set the height of every item of the list, to improve perfomance and later use in the getItemLayout
const ITEM_HEIGHT = 100;
// Data that will be displayed in the FlatList
const [data, setData] = React.useState<DataType>();
// The variable that will hold the reference of the FlatList
const flatListRef = React.useRef<FlatList>(null);
// The effect that will always run whenever there's a change to the data
React.useLayoutEffect(() => {
const timeout = setTimeout(() => {
if (flatListRef.current && data && data.length > 0) {
flatListRef.current.scrollToEnd({ animated: true });
}
}, 1000);
return () => {
clearTimeout(timeout);
};
}, [data]);
// Your FlatList component that will receive ref, data and other properties as needed, you also have to use getItemLayout
<FlatList
data={data}
ref={flatListRef}
getItemLayout={(data, index) => {
return { length: ITEM_HEIGHT, offset: ITEM_HEIGHT * index, index };
}}
{ ...otherProperties }
/>
With the example above you can have a fluid and animated scroll to bottom. Recommended for when you receive a new message and has to scroll to the bottom, for example.
Apart from this, the second and easier way is by implementing the initialScrollIndex property that will instantly loads the list at the bottom, like that chat apps you mentioned. It will work fine when opening the chat screen for the first time.
Like this:
// No need to use useEffect, timeout and references...
// Just use getItemLayout and initialScrollIndex.
// Set the height of every item of the list, to improve perfomance and later use in the getItemLayout
const ITEM_HEIGHT = 100;
<FlatList
data={data}
getItemLayout={(data, index) => {
return { length: ITEM_HEIGHT, offset: ITEM_HEIGHT * index, index };
}}
{ ...otherProperties }
/>
I found a solution that worked for me 100%
Added the ref flatListRef to my flatlist:
<Flatlist
reference={(ref) => this.flatListRef = ref}
data={data}
keyExtractor={keyExtractor}
renderItem={renderItem}
/>
Then whenever you want to automatically scroll to bottom of the list use:
this.flatListRef._listRef._scrollRef.scrollToEnd({ animating: true });
yes you should access the element _listRef then _scrollRef then call the scrollToEnd 🙄
react-native 0.64.1
react 17.0.2
I've struggled on this as well and found the best possible solution for me that renders without a glitch is:
Use inverted={-1} props
Reverse the order of messages objects inside my array with data={MyArrayofMessages.reverse()} in my case data={this.state.messages.reverse()} using reverse() javascript function.
Stupidly easy and renders instantaneously !
Use inverted={1} and reverse your data by using the JS reverse function. It worked for me
<FlatList contentContainerStyle={{ flex: 1, justifyContent: 'flex-end' }} />
I am guessing that RN cannot guess your layout so it cannot know how much it needs to "move". According to the scroll methods in the docs you might need to implement a getItemLayout function, so RN can tell how much it needs to scroll.
https://facebook.github.io/react-native/docs/flatlist.html#scrolltoend
Guys if you want FlatList scroll to bottom at initial render. Just added inverted={-1} to your FlatList. I have struggle with scroll to bottom for couple of hours but it ends up with inverted={-1}. Don't need to think to much about measure the height of FlatList items dynamically using getItemLayout and initialScrollIndex or whats so ever.
I found a solution that worked for me 100%
let scrollRef = React.useRef(null)
and
<FlatList
ref={(it) => (scrollRef.current = it)}
onContentSizeChange={() =>
scrollRef.current?.scrollToEnd({animated: false})
}
data={data}/>
If you want to display the message inverted, set "inverted" to true in the flat list.
<Flatlist
data={messageData}
inverted={true}
horizontal={false}
/>
If you just want to scroll to the last message, you can use initialScrollIndex
<Flatlist
data={messageData}
initialScrollIndex={messageArray.length - 1}
horizontal={false}
/>
I spent couple of hours struggling with showing the first message on top without being able to calculate the item's height as it contains links and messages. But finally i've been able to...
What i've done is that i wrapped the FlatList in a View, set FlatList as inverted, made it to take all available space and then justified content. So now, conversations with few messages starts at top but when there are multiple messages, they will end on bottom. Something like this:
<View style={ConversationStyle.container}>
<FlatList
data={conversations}
initialNumToRender={10}
renderItem={({ item }) => (
<SmsConversationItem
item={item}
onDelete={onDelete}
/>
)}
keyExtractor={(item) => item.id}
getItemCount={getItemCount}
getItem={getItem}
contentContainerStyle={ConversationStyle.virtualizedListContainer}
inverted // This will make items in reversed order but will make all of them start from bottom
/>
</View>
And my style looks like this:
const ConversationStyle = StyleSheet.create({
container: {
flex: 1
},
virtualizedListContainer: {
flexGrow: 1,
justifyContent: 'flex-end'
}
};