react native 100+ items flatlist very slow performance - javascript

I have a list just simple text that rendering into flatlist on react native but I am experiencing very very slow performance which makes app unusable.
How can I solve this? My code is:
<FlatList
data={[{key: 'a'}, {key: 'b'} ... +400]}
renderItem={({item}) => <Text>{item.key}</Text>}
/>

Here is my suggestions:
A. Avoid anonymous arrow function on renderItem props.
Move out the renderItem function to the outside of render function, so it won't recreate itself each time render function called.
B. Try add initialNumToRender prop on your FlatList
It will define how many items will be rendered for the first time, it could save some resources with lot of data.
C. Define the key prop on your Item Component
Simply it will avoid re-render on dynamically added/removed items with defined key on each item. Make sure it is unique, don't use index as the key! You can also using keyExtractor as an alternative.
D. Optional optimization
Try use getItemLayout to skip measurement of dynamic content. Also there is some prop called maxToRenderPerBatch, windowSize that you can use to limit how many items you will rendered. Refer to the official doc to VirtualizedList or FlatList.
E. Talk is Cheap, show me the code!
// render item function, outside from class's `render()`
const renderItem = ({ item }) => (<Text key={item.key}>{item.key}</Text>);
// we set the height of item is fixed
const getItemLayout = (data, index) => (
{length: ITEM_HEIGHT, offset: ITEM_HEIGHT * index, index}
);
const items = [{ key: 'a' }, { key: 'b'}, ...+400];
function render () => (
<FlatList
data={items}
renderItem={renderItem}
getItemLayout={getItemLayout}
initialNumToRender={5}
maxToRenderPerBatch={10}
windowSize={10}
/>
);

Try out this listview https://github.com/Flipkart/ReactEssentials, it renders far fewer items than FlatList and then recycles them. Should be much faster.
npm install --save recyclerlistview

check this link
https://github.com/filipemerker/flatlist-performance-tips
Example
FlatList
containerContentStyle={styles.container}
data={countries}
renderItem={({ item }) => (
<View style={styles.results}>
<Results
{...this.props}
country={item}
handleUpdate={this.handleUpdate}
pendingCountry={pendingCountry}
/>
</View>
)}
keyExtractor={item => item.alpha2code}
ListHeaderComponent={() => this.renderHeader()}
// Performance settings
removeClippedSubviews={true} // Unmount components when outside of window
initialNumToRender={2} // Reduce initial render amount
maxToRenderPerBatch={1} // Reduce number in each render batch
updateCellsBatchingPeriod={100} // Increase time between renders
windowSize={7} // Reduce the window size
/>

One of the simple ways to optimize your flatlist is by using React.memo. In technical words, it basically does a shallow comparison of your data and check whether they needs to be re-rendered or not.
Make a file such as ListComponent.js and add the renderItem JSX to it, and and it to the renderItem.
// ListComponent.js
import React, { memo } from "react";
import { StyleSheet, Text, View } from "react-native";
const ListComponent = ({ item }) => {
return <View ></View>
};
export default memo(ListComponent);
Here is your FlatList
<FlatList
data={data}
removeClippedSubviews={true}
maxToRenderPerBatch={8}
windowSize={11}
initialNumToRender={8}
keyExtractor={keyExtractor}
renderItem={({ item }) => (
<ListComponent item={item} />
)}
/>

Another optimization would be to provide a key using keyExtractor prop. It's very important.

I used 'react-native-optimized-flatlist'
and my problem was solved, the only thing to be careful about is that when you use this package, it removes keyExtractor and extraData

You can use react-native-optimized-flatlist. It is the optimized version of Flatlist.
1) Add this package by :
npm i -S react-native-optimized-flatlist
OR
yarn add react-native-optimized-flatlist
2) Replace <FlatList/> by <OptimizedFlatlist/>

Related

React Native doesn't trigger useEffect when updating data (Screen isn't rerendered)

I'm stuck at a problem - I'm building a receipes app with Firebase Realtime. I've a working prototype, but I'm stuck with an issue where useEffect won't trigger a reload after editing the array [presentIngredients].
This how my presentIngredients is defined (note that presentIngredient is used to store the current user input before the user adds the ingredient. After that, the presentIngredient get's added to the presentIngredients!):
const [ presentIngredients, setPresentIngredients ] = useState([]);
const [ presentIngredient, setPresentIngredient ] = useState('');
My useEffect hook looks like that:
useEffect(() => {
console.log('useEffect called!')
return onValue(ref(db, databasePath), querySnapshot => {
let data = querySnapshot.val() || {};
let receipeItems = {...data};
setReceipes(receipeItems);
setPresentIngredients(presentIngredients);
})
}, [])
Here's my code to render the UI for adding/removing existing ingredients:
{ /* adding a to-do list for the ingredients */ }
<View style={styles.ingredientsWrapper}>
{presentIngredients.length > 0 ? (
presentIngredients.map((key, value) => (
<View style={styles.addIngredient} key={value}>
<Text>{key} + {value}</Text>
<TouchableOpacity onPress={() => removeIngredient(key)}>
<Feather name="x" size={24} color="black" />
</TouchableOpacity>
</View>
))
) : (
<Text style={styles.text}>No ingredients, add your first one.</Text>
)}
<View style={styles.addIngredientWrapper}>
<TextInput
placeholder='Add ingredients...'
value={presentIngredient}
style={styles.text}
onChangeText={text => {setPresentIngredient(text)}}
onSubmitEditing={addIngredient} />
<TouchableOpacity onPress={() => addIngredient()}>
<Feather name="plus" size={20} color="black" />
</TouchableOpacity>
</View>
</View>
And this is my function to delete the selected entry from my presentIngredients array or add one:
// update the ingredients array after each input
function addIngredient() {
Keyboard.dismiss();
setPresentIngredients(presentIngredients => [...presentIngredients, presentIngredient]);
setPresentIngredient('');
}
// remove items by their key
function removeIngredient(id) {
Keyboard.dismiss();
// splice (remove) the 1st element after the id
presentIngredients.splice(id, 1);
setPresentIngredients(presentIngredients);
}
The useEffect hook isn't triggered when adding an ingredient, however the change is instantly rendered on the screen. If I delete an item, the change isn't noticeable until I reload the screen - what am I doing wrong?
Note that all this is happening before data is send to Firebase.
Issues
There are a few overt issues I see with the code:
The code uses the array index as the React key, so if you mutate the array, i.e. add, remove, reorder, etc... the index values won't be the same as they were on a previous render cycle per array element. In other words, the React key is the same regardless what value is now at any given index and React likely bails on rerendering.
The removeIngredient callback handler is mutating the existing state instead of creating a new array reference.
Solution
Using the array index as a React key is bad if you are actively mutating the array. You want to use React keys that are intrinsically related to the data so it's "sticky" and remains with the data, not the position in the array being mapped. GUIDs and other object properties that provide sufficient uniqueness within the data set are great candidates.
presentIngredients.map((el, index) => (
<View style={styles.addIngredient} key={el.id}>
<Text>{el.key} + {index}</Text>
<TouchableOpacity onPress={() => removeIngredient(el.id)}>
<Feather name="x" size={24} color="black" />
</TouchableOpacity>
</View>
));
Use Array.prototype.filter to remove an element from an array and return a new array reference.
function removeIngredient(id) {
Keyboard.dismiss();
setPresentIngredients(presentIngredients => presentIngredients.filter(
el => el.id !== id
));
}
Note above that I'm assuming the presentIngredients data element objects have a GUID named id.
Listening for state updates
The useEffect hook isn't triggered when adding an ingredient, however
the change is instantly rendered on the screen.
The single useEffect only exists to run once when the component mounts to fetch the data and populate the local state. If you want to then issue side-effects when the state updates later you'll need a second useEffect hook with a dependency on the state to issue the side-effect.
Example:
useEffect(() => {
console.log("presentIngredients updated", { presentIngredients });
// handle side-effect like updating the fire store
}, [presentIngredients]);
you need to create another useEffect, because the first useEffect need to be called only once (because you are setting a watcher).
useEffect(() => {
// this effect will be executed when presentIngredients change
}, [presentIngredients]);
if you don't remember how the array of dependencies work
you can check the explanation here
https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects

React Native FlatList ignoring item spacing

I'm really struggling to figure out why i can't get my FlatList to render items the way that I would expect.
I'm using react-query in order to fetch a data array. I pass this array into my FlatList to be rendered, then I use a render function to render a list item. However my flat list completely ignores all of the item's padding and margin, sometimes.
Here's a snippet to better explain what I'm trying to do:
function Component() {
data = [
{a: true, otherStuff},
{b: true, otherStuff}
]
function renderItem({item, index}) {
if (item.a) {
return <ListItemA/>
}
if (item.b) {
return <ListItemB/>
}
render(
<FlatList
renderitem={renderItem}
data={data}
/>
)
}
if you will upload the components code, it will be more clear.
try to use item separator and add spacing there
https://reactnative.dev/docs/flatlist#itemseparatorcomponent a

Difficulty understanding the value of removing arrow functions () =>

When I search the internet for react-native optimizations / best practices (Especially for FlatLists which are often greedy), I always find the advice not to use the arrow functions <Component onPress={() => ... }.
Example 1 : https://reactnative.dev/docs/optimizing-flatlist-configuration#avoid-anonymous-function-on-renderitem :
Move out the renderItem function to the outside of render function, so it won't recreate itself each time render function called. (...)
Example 2 : https://blog.codemagic.io/improve-react-native-app-performance/ :
Avoid Arrow Functions : Arrow functions are a common culprit for wasteful re-renders. Don’t use arrow functions as callbacks in your functions to render views (...)
Example 3 : https://medium.com/wix-engineering/dealing-with-performance-issues-in-react-native-b181d0012cfa :
Arrow functions is another usual suspect for wasteful re-renders. Don’t use arrow functions as callbacks (such as click/tap) in your render functions (...)
I understand that it is recommended not to use arrow function (especially in onPress button and FlatList), and to put the components outside of the render if possible.
Good practice example :
const IndexScreen = () => {
const onPress = () => console.log('PRESS, change state, etc...')
return (
<>
<Button
onPress={onPress}
/>
<FlatList
...
renderItem={renderItem}
ListFooterComponent={renderFooter}
/>
</>
)
}
const renderItem = ({ item: data }) => <Item data={data} ... />
const renderFooter = () => <Footer ... />
export default IndexScreen
But, often, I have other properties to integrate into my child components. The arrow function is therefore mandatory:
const IndexScreen = () => {
const otherData = ...(usually it comes from a useContext())...
<FlatList
...
renderItem={({ item: data }) => renderItem(data, otherData)}
/>
}
const renderItem = (data, otherData) => <Item data={data} otherData={otherData} />
export default IndexScreen
In the latter situation, are good practices followed despite the presence of an arrow function ?
In summary, if I remove otherData (for simplicity), are these two situations strictly identical and are good practices followed ?
Situation 1 :
const IndexScreen = () => {
return (
<FlatList
...
renderItem={renderItem}
/>
)
}
const renderItem = ({ item: data }) => <Item data={data} ... />
export default IndexScreen
=== Situation 2 ?
const IndexScreen = () => {
return (
<FlatList
...
renderItem={({ item: data }) => renderItem(data)}
/>
)
}
const renderItem = (data) => <Item data={data} ... />
export default IndexScreen
The answer has nothing to do with arrow functions, but rather understanding reference equality why react might decide to rerender a component.
You can use useCallback to wrap your function. This will cause the reference to renderItem to only update when one of your callback dependencies updates.
const renderItem = useCallback(()=>{
...
},
[otherdata]);
The first situation is ideal, because when your app code runs it will create only one renderItem function. In the second situation, even if it doesn't have otherProps, you're not following the good practice because a new function is created every render in this line
renderItem={({ item: data }) => renderItem(data)}
Hence, making the FlatList rerender every time.
To fix it, you need to memoize the function you pass in the renderItem prop with useCallback
const renderItem = useCallback(({ item: data }) => {
return (<Item data={data} />)
}, []);
...
<FlatList
...
renderItem={renderItem}
/>
so the memoized version will be created only once when the component mounts. And, if you need to inject more data in the render function, you define that data as a dependency of the useCallback hook to create the function only when that data changes, reducing rerenders down the tree.
const renderItem = useCallback(({ item: data }) => {
return (<Item data={data} otherData={otherData} />)
}, [otherData]);
to me as pointed out previously in other answers the issue is mainly due to the fact that if you use arrow functions inside the code, the function get redefined every time.
Also this way of defining a function make this function unnamed so this is harder to track while debugging: on error stack trace you can see the name of a named function directly in the code.
const renderItem = useCallback( function renderItemFunction ({ item: data }) {
return (<Item data={data} otherData={otherData} />)
}, [otherData]);
this way in the stack trace of errors you should see renderItemFunction indication

React Native FlatList key

I have a FlatList which renders a custom component "Card". I am passing as keyExtractor this function:
const keyExtractor = (item) => item.id;
And my renderItem function looks like this:
const renderItem = ({item, index}) => <Card {...item} />
My question is: should I pass a key to the Card component? I mean, should I do this
const renderItem = ({item, index}) => <Card key={item.id} {...item} />
to avoid my flatlist re-render components and improve the performance of my list? I have seen some people doing this... but I have never done it. If the answer is yes, why we need keyExtractor if each rendered component have a key?
Of course, if I pass a key to my custom component I will do this on its implementation:
return <View key={props.key}>...</View>
Thank you.
You should have a unique property in your data and use it as a key. There is no need to pass the key to your Card component.
If your data is something like this:
data = [
{
id: 'a12f56e5',
name: 'me',
age: 24
}
// ...
];
then you can use id as key.
Also, please take a look at this to improve your FlatList performance

Flat List not maintaining unique List?

I wanted to know if KeyExtrator contributes to maintain the unique list ?
if yes then its not helping me at all.
I am trying to display elements on flat list when a new Item comes in, the list just addes it without checking for uniqueness of the list
Main goal is to make my list unique, I had tried ListView It was working
due to logic implemented using rowHasChanged method.trying to see if there is anything for FlatList.
constructor(props) {
super(props);
this.state = {
TESTDATA: [{
BarCode: '1',
StopAddress: 'Label 1'
}]
};
};
onReceivedMessage(messages) {
var jsondata = JSON.parse(messages)
var dataTest = this.state.TESTDATA;
var Data = addressToDataMap.get(dataTest.BarCodes);
dataTest.push(jsondata);
this.setState({
TESTDATA: dataTest
});
}
<FlatList
extraData={this.state}
data={this.state.TESTDATA}
keyExtractor={(item, index) => item.BarCode.toString()}
renderItem={({ item}) => (
<Text style={styles.baseText}>
Stop Address: {item.StopAddress}{'\n'}
Barcodes: {item.BarCode}
</Text>
)}
/>
I wanted to know if KeyExtrator contributes to maintain the unique list ? NO is the Answer
Explanation : keyExtractor tells the list to use the ids for the react keys instead of the default key property. Keys help React identify which items have changed, are added, or are removed. Key also used for caching and as the react key to tracking item re-ordering. These unique keys are what allow the VirtualizedList (which is what FlatList is built on) to track items and are really important in terms of efficiency. The keyExtractor falls back to using the index by default. For example
<FlatList
style={{}}
data={this.state.FeedDataCollection}
keyExtractor={(item, index) => item.key}
renderItem={(rowData) =>this.RenderFeedCard(rowData)}
/>
Here item.key is is key.So if you want display elements based on the uniqueness, you have write a logic for that. keyExtractor cant do that. Learn more about React Keys here https://reactjs.org/docs/lists-and-keys.html and KeyExtractor here https://facebook.github.io/react-native/docs/flatlist#keyextractor

Categories