Flatlist freezes when `onEndReached` is triggered - react-native

I'm using react native and I have a flatlist that can render anywhere from 2 items to over 2000. Due to this, it's really important to optimize the performance to make sure the Flatlist doesn't lag. I've tried to optimize performance with the props provided in the docs. However, I noticed when I scroll down to the bottom and my loadMoreData function is called, scrolling freezes for like 1 second. How can I make it so this doesn't happen? Thank you.
Code:
const [data, setData] = React.useState(mockData);
// Load more data
const loadMoreData = () => {
setData((prevItems) => {
return [...new Set([...prevItems, ...testItem])]; // 'testItem' is an array with one object that
});
};
return (
<FlatList
extraData={data}
data={data}
getItemLayout={(data, index) => ({
length: 78,
offset: 78 * index,
index,
})}
initialNumToRender={7}
maxToRenderPerBatch={10}
windowSize={10}
onEndReached={loadMoreData} // freezes when this is called
onEndReachedThreshold={0.1}
removeClippedSubviews={true}
keyExtractor={(item, index) => index.toString()}
renderItem={({ item, index }) => (
<>
<Swipeable renderRightActions={(progress, dragX) =>}>
<HoldItem items={InventoryHoldMenuItems}>
<Layout>
<InventoryItem item={item} />
</Layout>
</HoldItem>
</Swipeable>
</>
)}
/>
)}
)

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>

React native FlatList not rerendering when data prop changes to empty array

I have a FlatList with a data prop pulling from Redux
render() {
return (
<View>
<FlatList
data={this.props.arrayOfPlacesFromRedux}
renderItem={({item}) => {.......
Whenever I dispatch changes to arrayOfPlacesFromRedux(i.e. adding or removing children), the FlatList rerenders....UNLESS I remove all children from array (i.e. make length zero).When arrayOfPlacesFromRedux changes from a positive length to a length of zero, the FlatList does not rerender.....however all other types of changes to array do indeed cause FlatList to rerender
UPDATE 02/27
Below is my reducer used to update Redux arrayOfPlacesFromRedux
const reducer = (state = initialState, action) => {
switch (action.type) {
case UPDATE_PLACES_ARRAY:
return {...state, arrayOfPlaces: action.payload};
default:
return state;
}
};
In the situation noted above when FlatList does not rerender.....action.payload is an empty array
The question is missing some important piece of code.
React as well as Redux need arrays reference to change, meaning for a component to reRender on state change, the array references needs to change.
Live demo at https://snack.expo.dev/RrFFxfeWY
Here is the most interesting parts:
If you have a basic component as below:
const MyList = () => {
const [data, setData] = React.useState([
'#FF0000',
'#FF8000',
'#FFFF00',
]);
return (
<>
<Text>List poping is not working</Text>
<FlatList
data={data}
renderItem={({ item }) => (
<Pressable
onPress={() => {
data.pop(); // Does not work because we are not changing it's ref
}}
style={{ backgroundColor: item, padding: 8 }}>
<Text>{item}</Text>
</Pressable>
)}
/>
</>
);
};
The data need to have a new array reference as below. data2.filter(..) will return a new array, we are not changing the data2 base values, just creating a new array with one item less.
const MyList = () => {
const [data2, setData2] = React.useState([
'#00FFFF',
'#0080FF',
'#0000FF',
]);
return (
<>
<Text>List WORKING!</Text>
<FlatList
data={data2}
renderItem={({ item }) => (
<Pressable
onPress={() => {
setData2(data2.filter(dataItem => dataItem !== item)) // works
//setData2([]); // Also works
}}
style={{ backgroundColor: item, padding: 8 }}>
<Text>{item}</Text>
</Pressable>
)}
/>
</>
);
};
A library like Immer.js simplify the manipulation of states to mutate the object, and immer will created a new reference for you.
Oh no rookie mistake that wasted everyones time!!
I was implementing shouldComponentUpdate method that was stopping Flatlist rendering :(
Thanks for all for the answers
You may need to use ListEmptyComponent, which is a prop that comes with FlatList, src.
Honestly, I'm not sure why it does not re-render when you update your state, or why they added a specific function/prop to render when the array is empty, but it's clear from the docs that this is what's needed.
You can do something like this:
<SafeAreaView style={styles.container}>
<FlatList
data={DATA}
renderItem={renderItem}
keyExtractor={(item) => item.id}
extraData={selectedId}
--> ListEmptyComponent={() => <MyComponent />}
/>
</SafeAreaView>

ReactNative ScrollToIndex too slow

im using react native to create an app for a legal document. i need the document to be scrollable. i rendered it using VirtualizedList.
thing is when i try to use scrollToIndex(index: 'something') performance is too slow.
my list contains about 4000 rendered items (each being about a paragraph long).
is there any way to make this run smoother?
export default function App() {
const scroller = useRef();
return (
<SafeAreaView>
<View style={styles.upperContainer}>
<CustomButton
onPress={() => {
scroller.current.scrollToIndex({ index: 1547 });
}}
/>
</View>
<View style={styles.flatContainer}>
<VirtualizedList
ref={scroller}
data={data}
renderItem={({ item }) => (
<CustomText data={item.content} type={item.type} />
)}
getItem={(data, index) => data[index]}
getItemCount={(data) => data.length}
keyExtractor={(item) => item.number.toString()}
initialNumToRender={4000}
onScrollToIndexFailed={() => {
alert('error');
}}
/>
</View>
</SafeAreaView>
);
}
This question is similar to these ones and maybe you can reference some of the answers and see what works for your case. Basically, you need a pure component to make this work.
Link - Flatlist performance slow
Link - VirtualizedList: You have a large list that is slow to update

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

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>