Render FlatList of Videos in a performant way - react-native

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

Related

Why is data from useQuery undefined when I start screen?

When I start screen, I get data from useQuery
And by using useEffect, I get data and immediately sort this data.
After sorting data, I put this data to useState named flatlistdata.
And I draw screen by using Flatlist with this data.
Flatlist data should be sorted.
So I need to refined data before.
const { data: allFeedData, refetch: allFeedRefetch } = useQuery(SEE_ALL_FEED_ORDER);
const [flatlistdata, setFlatlistdata] = useState([]);
useEffect(() => {
allFeedRefetch();
setFlatlistdata(
[...allFeedData.seeAllFeedOrder].sort(function (a, b) {
return b.directFeedNumber - a.directFeedNumber;
})
);
}, []);
<FlatList
data={flatlistdata}
keyExtractor={(item) => item.id}
renderItem={RankRow}
refreshing={refreshing}
onRefresh={refresh}
/>
However when I click screen, it says undefined is not an object which means data is empty.
I think the problem here is that screen is drawn, before sorting data and putting it to useState...?
I need a little hint 😭
Please help me.
The query is an asynchronous operation, so, the data variable will always start out as undefined while the network request completes. There are two possible solutions.
In your useEffect, only set your state if data is defined. Wrap your setFlatlistData with if (allFeedData?.length) { ... }
Use the React Query selection prop to have Query do the same operation before allFeedData gets populated. Check the select prop in the docs for useQuery: https://react-query-v2.tanstack.com/reference/useQuery

Refresh a Flatlist if data from function inside componentDidMount is changed

Good day SO.
I am a beginner in React Native so my terminology is very lacking. The project was inherited from previous programmer.
I have an async functions
getData = async () => {}
Then here:
componentDidMount() {
this.getData ();
}
Then last is Flatlist
<FlatList
data={this.state.somedata}
renderItem{({item, index}) => {}} />
My question is, if data is changed, how should I update my flatlist? I read that I need to add extraData but I dont know what variable should I add inside.
Note I know that componentDidMount only runs once on the first render. But I dont know how to proceed.

How to prevent React Native FlatList consumer from bloating app memory due to state data increase?

I am new to React Native and FlatList and all the examples and tutorials I can find are very basic and don't answer my question, which is: Can FlatList be fed with dynamic data without increasing the memory of the application. If I hold the data into a data array, and that array is part of the state of the component that uses the FlatList, then adding dynamic data to thatdata will not scale and will consume the whole memory of the application. In other words, if I have a component like:
const MyApp = () => {
const [data, setData] = useState([]);
useEffect(() => {
....load 10 objects from network...
setData(data.concat(newData));
}, []);
const onPress = () => {
....load 10 more....
setData(data.concat(newData));
}
return (
<View>
<FlatList data={data} .../>
<Button onPress={onPress} title="Load More..."/>
</View>
)
}
don't seem to scale for me. data array increases memory consumption of the app every time the Load More... button is clicked.
Am I wrong? If no, then what is the best practice to solve this problem?
If yes, why am I wrong?
Thank you very much in advance.
P.

performance issues with React-Native flatlist with 100+ list items

I am trying to use RN flatlist to show a large list of contact names (100+ items). I keep getting the following warning:
VirtualizedList: You have a large list that is slow to update - make
sure your renderItem function renders components that follow React
performance best practices like PureComponent, shouldComponentUpdate,
etc.
Certain animated UI items get very sluggish whenever the list has more than 50 items in it, however the sluggishness gets a lot better once I scroll all the way down to the bottom of the list
I grab all the contacts in one go and store them in an array in a redux store. I have tried using props like initialNumToRender but cant seem to improve performance. Is there anything I can do to improve my list? I have never used RN FlatList before so any tips would be appreciated
Here is my list related code:
renderRow = ({item}) => {
return (
<ListItem
title={item.firstName}
chevronColor={colors.tertiary}
/>
)
}
<FlatList
data={this.props.contacts}
renderItem={this.renderRow}
keyExtractor={item => item.id}
initialNumToRender={10}
removeClippedSubviews={true}
/>
this.props.contacts is the array of contact objects in the redux store
I ended up implementing recyclerlistview and followed this tutorial which explained how to get it working since the documentation is lacking. Its working miles better than flat list did. Very fast and smooth.
You can follow these code implementations.
For VIEWABILITY_CONFIG follow this link
const VIEWABILITY_CONFIG - {
minimumViewTime: 300,
viewAreaCoveragePercentThreshold: 100,
waitForInteraction
}
const yourItemHeight = 108;
class yourClass extends React.PureComponent
...
_getItemLayout(data, index) {
return { lenght: yourItemHeight, offset: yourItemHeight * index, index }
}
<FlatList
data={this.props.contacts}
renderItem={this.renderRow}
keyExtractor={item => item.id}
getItemLayout: {this._getItemLayout}
initialNumToRender={10} // Vary According to your screen size take a lumsum according to your item height
removeClippedSubviews={true}
viewabilityConfig={VIEWABILITY_CONFIG}
/>

React Native mobx binding to FlatList not working

I have a RN (0.44.2) mobx (3.1.10) app which uses a FlatList. I'm basically following https://blog.callstack.io/write-react-native-apps-in-2017-style-with-mobx-e2dffc209fcb
When using my own store, opposed to the examples, I'm having to use toJS() in order to get the FlastList to render
// renders list
<FlatList
data={this.props.giphyStore.images.toJS()}
keyExtractor={(_, i) => i}
renderItem={({ item }) => <Text>found the data</Text>}
/>
// does not render list
<FlatList
data={this.props.giphyStore.images}
keyExtractor={(_, i) => i}
renderItem={({ item }) => <Text>did not find the data</Text>}
/>
I'm really struggling to figure out why toJS() might be needed in some cases and not others.
My store is setting the images observable like this
async getImageList(query: string) {
try {
const requestURL = `${constants.GIPHY_ENDPOINT}${query}`
const response = await axios.get(requestURL);
const imgs = response.data.data.map((item) => {
return { id: item.id, url: item.images.downsized.url }
})
this.images.replace(imgs)
} catch (e) {
}
}
As a follow up question, I'm not sure why I need to do the following this.images.replace(imgs) where as in the tutorial he simply did does this.tracks = response.data.tracks.items which triggers the observable just fine.
If anyone has suggestions, I would very much appreciate it.
This is because mobx's arrays are objects and the data in FlatList or in react native expects an array. You can read more about it in here and there.
Also..., slice returns a shallow copy; a new array with the same contents, while toJS also converts the values inside the array (but only if they are observables).
This question is kinda old, but it's also worth mentioning that MobX only tracks the render function by default, while FlatList accepts rendering callbacks and calls them. (eg renderItem={this.renderItem})
In order for items to update without the whole list refreshing, wrap the render callback's result with <Observer>.
See Understanding reactivity [Mobx docs]