Load more when the top is reached - react-native

As we can use load more functionality when onEndReached, the same way how can we use load more when we reach to the top of the list ?
Also while doing this when I load more data at the top, Flat list moves to the very top element and hence scroll becomes infinite.
thanks.

1) You can use onScroll callback and get offset from top from event.nativeEvent.contentOffset.y
0 means the top point
For example:
<FlatList
data={messages}
onScroll={
(event: NativeSyntheticEvent<NativeScrollEvent>) =>
onContentOffsetChanged(event.nativeEvent.contentOffset.y)
}
/>
onContentOffsetChanged = (distanceFromTop: number) => {
distanceFromTop === 0 && makeSomething();
}
2) Or you can use "inverted" property and onEndReached will call at top of the screen
inverted
Reverses the direction of scroll. Uses scale transforms of -1.

Yes, we can achieve this in React native 0.60 and above, since the significant changes been made in the Virtual list. Still its doesn't contain the solution for Android.

You can achieve it by inverted prop. When you FlatList is inverted your onEndReached would be called when you reach the top.

Related

Render FlatList of Videos in a performant way

I am using a react native with expo. I have a lot of videos that I need to render (sort of like TikTok does). When I fetch about 30 videos and put them in the flat list in the renderItem method, it gets stuck and luggish. I was thinking about getting an amount of videos but sending to the renderItem method only 3 videos each time, and when the user will scroll down and reach index 2 it will shift the first index and append the fourth video from the fetched one. The idea was to have a small array of size 3 and change the items in it every scroll, in order to prevent rendering all the videos at once. That required array manipulation and caused a rerender each time the array of videos was updated(each change made sort of a flash - what was indicating a whole rerender).
My question is how should it be implemented in order the transition between the videos to be as fast and clean as possible from the client side perspective? What is the correct way to render videos in a flat list so it won't be stuck? I dont think It should be done that way, there has to be a better way.
This is what I have tried:
// challenges is an array coming from a fetch, just sliced it for the purpose of the example
// suppose it is an array that contains 30 items
const [currentVideos, setCurrentVideos] = useState([challenges.slice(0,3)]);
<FlatList
data={currentVideos}
renderItem={renderItem}
keyExtractor={(challenge, i) => challenge._id}
showsVerticalScrollIndicator={false}
snapToInterval={Dimensions.get("window").height - UIConsts.bottomNavbarHeight}
snapToAlignment={"start"}
decelerationRate={"fast"}
ref={(ref) => {
flatListRef.current = ref;
}}
onScrollToIndexFailed={() => alert("no such index")}
onViewableItemsChanged={onViewRef.current}
onScrollEndDrag={() => (scrollEnded.current = true)}
onScrollBeginDrag={beginDarg}
></FlatList>
useEffect(() => {
// just wanted to check on 3 videos
if (currentlyPlaying === 2) {
let temp = currentVideos;
temp.shift(); // pop the top item
temp.push(challenges[4]) // append a new one
setCurrentVideos(temp);
}
}, [currentlyPlaying]);
const onViewRef = useRef(({ viewableItems }) => {
// change playing video only after user stop dragging
scrollEnded.current && setCurrentlyPlaying(viewableItems[0]?.index);
});
I would avoid manipulating the data array and doing business logic inside of the component.
Besides, you can achieve your desired behaviour without the need to manipulate your data array at all, with the maxToRenderPerBatch FlatList prop. As mentioned in the official RN docs for FlatList optimization techniques.
You should avoid using anonymous functions and objects inside of your component's properties, move them outside of the return statement and use the useMemo and useCallback hooks to avoid their unnecessary recreation on every re-render. For example instead of writing your code like this:
const App = () => {
return (
<FlatList
keyExtractor={(challenge, i) => challenge._id}
snapToInterval={Dimensions.get('window').height - UIConsts.bottomNavbarHeight}
/>
);
};
A better approach would be to re-write it to something like this:
const App = () => {
// Because of useCallback, the keyExtractor function will be memoized and won't recreate itself on every re-render
const keyExtractor = useCallback((challenge, i) => challenge._id, []);
// useMemo is almost the same as useCallback, but it is used to return non-function types
// Defining your snapToInterval variable like this will cause it to memoize its value and it
// won't recreate itself on every re-render
const snapToInterval = useMemo(() => Dimensions.get('window').height - UIConsts.bottomNavbarHeight, []);
return (
<FlatList
keyExtractor={keyExtractor}
snapToInterval={snapToInterval}
/>
);
};
If you haven't already, you should consider extracting the component returned from the renderItem function to a different file and applying React.memo to it.
Note: try not to overuse useCallback and useMemo. You can find good and detailed explanation of why not to overuse them here and here.
If you're able to, you should optimize your videos before uploading them to the server. You can optimize your client side part of the app as much as you want, but if the content isn't properly optimized, you won't be able to achieve a smooth and performant experience regardless of your efforts.
Here's also some articles describing how you can optimize your FlatList component:
How did I optimize my React Native FlatList?
8 ways to optimize React native FlatList performance
Optimizing a React Native FlatList With Many Child Components
React Native Performance Optimisation With Hooks
React Native: Optimized FlatList of videos
I hope that some of this will be helpful to you. Good luck.
I have been searching for a solution as well. I have worked out a solution based on some previous work using InViewPort. you can check it out here https://github.com/471Q/React-Native-FlatList-Video-Feed

Controlling how far to scroll in flat list. React native

Good day guys , is there anyway I can control how far the flatlist can scroll ? I'm trying to make an image viewer. When there is multiple image , the user can scroll to the 2nd image without reaching the 3rd image. Thank you. Expected outcome :
Either you can use a library like react native snap carousel
or use the function of scrollToIndex inside any function ,so that you can control which index the user goes ==
scrollToNext(){
this.flatListRef.scrollToIndex({animated: true, index: newIndex});
}
<Flatlist
ref={ref => {
this.flatListRef = ref;
}}
/>
hope it helps. feel free for updates

React native synchronize two flatlist smoothly

I have two flatlist, one contains the data (dataScroll) and the other the checkboxes (checkScroll) for every data item.
The reason for this is that the checkboxes have to be always visible while the user scrolls horizontaly on the dataScroll (I put the dataScroll in a horizontally scrollable scrollview).
Demo:
expo snack demo
Tried so far:
On dataScroll's scroll event, I got the y offset and moved the checkScroll to that y position.
handleDataScroll = ({ nativeEvent: { contentOffset: { y } } }) => {
this.checkScroll.scrollToOffset({ offset: y, animated: true });
}
It (almost) does the job, but there is a huge delay between the 2 flatlist while scrolling.
--
I read that maybe the use of animated components the way to go, but I couldn't figure out how the animation works in react native.
So I'd like to get some help on how should I bind the two flatlist together so that if I scroll on one list, the other follows it with no (or at least minimal) delay.
If only the dataScroll flatlist is scrollable that's ok too.

ReactNative [Flatlist] scrollToOffset, how do I figure out offset position of clicked row?

I want a row in a FlatList to scroll to the top of the screen whenever a user clicks on it. I am currently doing this by using the scrollToIndex method but the problem is that I have to animate the top margin (from 10 to 0) to get it flushed to the top of the screen. I also have an issue where if a user clicks on a row to expand the row, and a previous row was already expanded, the previous row will collapse causing the original scrollToIndex to animate to some position off the top of the screen.
My current two alternatives are to trigger an additional scrollToIndex once the previous row finishes contracting or use scrollToOffset to figure out where to scroll to based on good old math values. Is there a way to figure out row offset positions by index or some other value whenever it is clicked?
Thanks for any help anybody might be able to provide!
You can use scrollToItem to achieve what you are trying to achieve
So suppose you have this FlatList component
<FlatList
ref={(ref) => { this._flatList = ref; }}
data = {dataSourche}
renderItem = {( info: {item:item, index:number}) => this._renderFlatListItem(info.item, info.index)}
keyExtractor = {(item) => item.id}
decelerationRate={0}
snapToInterval={cardHeight}
snapToAlignment={"start"} //Can also be 'center' or 'end'
onMomentumScrollEnd = {(event) => this._onMomentumScrollEnd(event)}
getItemLayout = {this.getItemLayout}/>
You will need to define a method getItemLayout that will tell the flatList 3 things
length: In your case height of the card
offset: this is distance from top of the list to current card usually we can set it as length * index of the card
index of the card
so this is a sample getItemLayout which you can refer to
getItemLayout = (data, index) => (
{ length: 170, offset: 170 * index, index }
);
Next will be your FlatList scrollToItem call
this._flatList.scrollToItem({
animated:true, //can also be false
item:item,
viewPosition:0 //this is the first position that is currently attached to the window
})
Instead of trying to figure out where to scroll to using the scrollToOffset method, I'm still using the scrollToIndex method and use the viewOffset parameter in order to calculate how much offset to add to the method: the difference between the expanded and collapsed heights of any other currently expanded item (if any) and subtracting the top margin of the clicked item.

React native detect screen rotation

I'm using onLayout to detect screen orientation and it's working fine inside my root view, but when I implemented inside the drawer it didn't work, any reason why this happens ?
code :
import Drawer from 'react-native-drawer'
...
onLayout(e) {
console.log('onLayout');
}
<Drawer onLayout={this.onLayout}
It didn't log any thing when orientation changed!
This is because the Drawer component doesn't take onLayout as a prop. You can see in the source code that the rendered View does use onLayout, but it's not pulling from something like this.props.onLayout.
I'm not exactly sure what you're looking to do, but maybe this issue will help you. As it shows, you can pass a function into openDrawerOffset instead of an integer or a ratio in order to be a little more dynamic with how you set your offset:
openDrawerOffset={(viewport) => {
if (viewport.width < 400) {
return viewport.width * 0.1;
}
return viewport.width - 400;
}}
You might also benefit from the Event handlers that react-native-drawer has to offer.