Swipe up or pull up to refresh in React-Native - react-native

I am trying to implement a feature in which whenever I went to end the end of the FlatList and then when I swipe up or pull up for some small time, then this acts as a refresh and required data get loaded. Also the flatlist is long so we reach the end of the list in some time. Please help in this as I can't get any resources available for the same.
I tried using various packages like react-native-gesture-handler etc. but couldn't get the solution which I am hoping for.

Reaching the end of FlatlList amd pulling up are two different thing
For Reaching end of the list You can detect by onEndReached callback.
For Refreshing (Pull to refresh) You can use [RefreshControl][1]
Please see the below Example
// Logic for onEndReached
const onEndReached= ()=>{
do Your styff
}
<FlatList
refreshControl={
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
}>
data={yourArray}
renderItem={yourRenderItem}
onEndReached= {onEndReached}
/>

you can use onMomentumScrollEnd which is provided by FlatList
const handleMomentumScrollEnd = () => {
clearTimeout(scrollTimeout.current);
if (prevY.current > 50) {
setRefreshing(true);
setTimeout(() => {
setRefreshing(false);
}, 2000);
}
};
you can check this expo snack link to view the full code.
Expo

Related

PagerView works on swipe but not programmatically with setPage

I am working with react-native-pager-view and I am having trouble setting the page programmatically using nativeEvent.setPage()
I have tried a lot of things, went through all the issues regarding it but no luck.
So far I've tried:
Changing the children to simple views exactly like the examples
Hard set the value in a useEffect and call it constantly
Use a separate value to keep track of current page
The swipe gesture is working fine on it and the pages transition, but if I press the button to go forward it doesn't.
If anyone has dealt with this component before please let me know if I'm missing anything.
Code that I'm trying to run. I've changed the View to simple react native view but it didn't work.
//Outside the component
const AnimatedPager = Animated.createAnimatedComponent(PagerView)
const [activeScreenKey, setActiveScreenKey] = useState(0)
const pagerViewRef = useRef<PagerView>(null)
// This function to sync the activeScreenKey if user swipes instead of clicking button
const getActiveKey = (e: PagerViewOnPageScrollEvent) => {
const position = e?.nativeEvent?.position
setActiveScreenKey(position)
}
// This calls the `setPage` and also puts it in a state var that I'm tracking
const setPage = (screenKey: number) => {
pagerViewRef.current?.setPage(screenKey)
setActiveScreenKey(screenKey)
}
<LinearGradient colors={['#3936BD', '#8448DB']} tw="flex-1">
<AnimatedPager
ref={pagerViewRef}
initialPage={0}
scrollEnabled={false}
onPageSelected={getActiveKey}
tw="flex-1"
>
<CustomView
key="1"
title={'title'}
subtitle={'subtitle'}
buttonTitle={'Next ➡️'}
onPress={() => setPage(1)}
/>
<CustomView
key="2"
title={'title'}
subtitle={'subtitle'}
buttonTitle={'Next ➡️'}
onPress={() => setPage(2)}
/>
</AnimatedPager>
</LinearGradient>

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

Append an element to the top of an inverted FlatList

I'm currently working on a React Native Chat App and I am trying to display the chat messages in a conventional way. I am using the Expo CLI along with Socket.io and my messages FlatList looks like following:
<FlatList
ref={chatRef}
data={messages}
keyExtractor={item => item._id}
showsVerticalScrollIndicator={false}
renderItem={renderItem}
onContentSizeChange={onContentSizeChange}
onEndReachedThreshold={0.2}
inverted
onEndReached={loadMore}
removeClippedSubviews={true}
/>
const onContentSizeChange = () => {
chatRef.current.scrollToOffset({ animated: true, offset: 0 })
}
My chat is working as expected and well displayed but whenever I send a new message it's been appended at the top of the FlatList.
When a new chat is sent, I dispatch an action to my reducer and I add the message as following:
{...}
case ADD_MESSAGE: return {
...state,
messages: [...state.messages, action.payload]
}
{...}
I am wondering if it is possible to append the new message at the end of the FlatList and scroll to the end of it.
Note: My messages array is order by newest to oldest
Thanks to anyone who can help me!
FlatList has a scrollToIndex method: https://reactnative.dev/docs/flatlist#scrolltoindex
Without seeing more of your code, it's hard to tell how you're reacting to props changes, but assuming you're using hooks, you could do something like:
React.useEffect(() => {
chatRef.current.scrollToIndex({{animated: true, index: messages.length - 1}})
},[messages])
That should scroll to the last message. If you want it to scroll to the first one, go to index: 0.
Regarding the order of your messages array, it seems like you have conflicting information; you said that it's ordered newest -> oldest, but in your action, you show [...state.messages, action.payload] which would seem to take the newest message onto the end of it. However, you can always switch the order ([action.payload,...state.messages])
Regarding inverted as one of the parameters to your list: I haven't experimented with this, but documentation says it "Reverses the direction of scroll." May want to be careful with how this affects your perception of ordering as well.
When updating your messages array, append the new message to the start
messages: [ action.payload, ...state.messages]

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

ReactNative FlatList onEndReached calling even when not scrolling

Im trying to implement Flatlist of Somedata which contains almost 200 elements in an array that im passing in data.
Im trying to give user the option to load the rest when they scroll only. but what happening with onEndReached is, it is calling even though we are not scrolling (I checked by doing console log). How can I make sure onEndReached is calling only when user scrolls.
I tried setting onEndReachedThreshold to the max of 5 and min of 0.01 in both the cases it is not working. Tried this too but didn't work https://github.com/facebook/react-native/issues/14015#issuecomment-310675650.
<FlatList
data={this.state.properties}
showsVerticalScrollIndicator={false}
keyExtractor={item => item.mlsnum}
renderItem={({ item }) => <Text{item.title}</Text>}
onEndReachedThreshold={0.01}
onEndReached={() => this.handleEndReach()}
/>
async handleEndReach() {
this.props.fetchProperties(pageNum) //call to my redux action to fetch the data
}
it's a bad implementation using async in there, you should use it in the function not in the callback. Please just use
onEndReached={() => this.fetchProperties())
and then for the function use
async fetchProperties(){
//do your async/await here
}