Performance : How to fix a custom checkbox component in a huge Flatlist in react native - react-native

I have used Flatlist component of react-native to render a huge list of checkboxes
These checkboxes (written in pure JS) essentially are mimicking the behaviour of checkboxes, everything works fine , but the performance of this is extremely slow.
What Can I do to improve the performance of this ?
the list usually renders about 1000 elements.
And here is the code:
<FlatList
style={{flex:1}}
data={finalData}
showsVerticalScrollIndicator={false}
renderItem={({item})=>{
var iconType = (this.state.selectedItems.indexOf(item.name)>-1?"ios-checkmark-circle":"ios-checkmark-circle-outline");
return(
<TouchableOpacity
onPress={
()=>{
var selectedItems = this.state.selectedItems;
var selectedItemsData = this.state.selectedItemsData;
if(selectedItems.indexOf(item.name)>-1){
const index = selectedItems.indexOf(item.name);
selectedItems.splice(index, 1);
selectedItemsData.splice(index,1);
}else{
selectedItems.push(item.name);
selectedItemsData.push({name:item.name,id:item.id});
}
this.setState({selectedItems});
this.setState({selectedItemsData});
}}>
<View style={{padding:20,flexDirection:'row',alignItems:'flex-start'}}>
<Icon name={iconType} color="#45AA6F" size={25} />
<Text style={{marginLeft:10,paddingTop:4,color:'#9B9B9B',fontWeight:'500'}}>{item.name}</Text>
</View>
<Dash style={{height:1}} dashColor="#45AA6F"/>
</TouchableOpacity>)}
}
/>

instead of using FlatList use ListView
you can use initialListSize to set initial list size when load so memory save there
also, you can use scrollRenderAheadDistance this will pre-render some elements ahead

So It appears the fix was extremely simple than I envisioned, all I needed to do was extend my component from PureComponent rather than Component.
I.e
class cropsOnProperty extends PureComponent
Instead of
class cropsOnProperty extends Component
Also shouldComponentUpdate helps in achieving the same result with far more granularity in control on when the render should happen
More can be found here

Related

Touchable view and activating another components animation

New to react native and in a component, I have a list of views that include a checkbox (react-native-bouncy-checkbox). Each view is wrapped in a TouchableWithoutFeedback(So I can click the entire view, not just the checkbox) and I have a boolean useState to tell the checkbox whether to display the check or not.
The issue I'm at is that I chose the library for the checkbox because the animation when it's clicked looks very nice. However, the animation doesn't play if I hit the view ~ only if I hit the actual checkbox, which is rather small in my app.
Is there any way to tell another component that it needs to act like it was pressed, so it can play its animation?
Code for clarity:
const Task = ({ id, text }: Types) => {
const [checked, setChecked] = React.useState(false);
return (
<TouchableWithoutFeedback onPress={() => setChecked(!checked)}>
<View style={styles.container} >
<BouncyCheckbox
disableBuiltInState={true}
isChecked={checked}
fillColor="blue"
iconStyle={{ borderColor: 'gray' }}
/>
<Text>{text}</Text>
</View>
</TouchableWithoutFeedback>
)
};
Okay figured it out. Apparently React native allows you to create refs to other components and you can use the reference.onPress() to activate the animation.

Is there a FlatList-like View without the scrolling?

Basically, I have a list of items (with images) inside a single Card (custom component). Because the rendering of those items is slow, I wanted to use a FlatList to render them incrementally.
Unfortunately, I get the expected error
VirtualizedLists should never be nested inside plain ScrollViews ...
But I don't actually want to use a ScrollView inside the Card. I just want to render a few Items in a single Card, which should change its size to fit all the items.
Setting scrollEnabled={false} on the FlatList still shows the error above.
Using the ListHeaderComponent and ListFooterComponent props is not an option, because the content above and below should NOT be rendered inside the Card.
Here is a minimal example of what I mean:
const Comp = () => {
return (
<ScrollView contentInsetAdjustmentBehavior="automatic">
<Text>Header</Text>
<Card>
<FlatList
data={data}
renderItem={({ item }) => (
<Image source={{uri: item.localImageUrl}}/>
)}
keyExtractor={(item) => item.id}
scrollEnabled={false}
initialNumToRender={0}
maxToRenderPerBatch={3}
contentInsetAdjustmentBehavior='automatic'
/>
</Card>
<Text>Footer</Text>
</ScrollView>
);
};
What's interesting though, that aside from the error - I get the result I expected, and I could technically hide that error and ignore it, but that does not seem like the recommended approach.
Important: I am not specifically looking for a FlatList solution. It could technically be any Component that renders items incrementally in a non-blocking way.
The important point with a Flatlist is the reusing of cells so that not all components need to be rendered at the same time. So scrolling is an important part of this. On the other hand two scrollable components inside eachother will make it impossible for the system to know which component should be scrolled.
If there are only 3 items and it should not be scrollable you can just include a list of items inside the Scrollview like this:
const Comp = () => {
return (
<ScrollView contentInsetAdjustmentBehavior="automatic">
<Text>Header</Text>
<Card>
{ data.map((item, index) => {
return (
<Text key={index}>{item.title}</Text>
);
}) }
</Card>
<Text>Footer</Text>
</ScrollView>
);
};

React Native Flatlist CellRendererComponent renders all at once?

I'm trying to make use of CellRendererComponent in Flatlist (rn 0.62) to let the zIndex prop to work, and it does, but all the data is rendered at once.
If I try to set initialNumToRender={number} and windowSize={number} then it limits the render, but doesn't render after the number set in those props.
<FlatList
style={{ flex: 1 }}
horizontal
showsHorizontalScrollIndicator={false}
data={data}
CellRendererComponent={renderGanttCell}
keyExtractor={ganttKeyExtractor}
bounces={false}
scrollEventThrottle={16}
initialNumToRender={3}
windowSize={9}
/>
Changing CellRendererComponent to renderItem works without the zIndex, but all the data is lazy rendered.
Any work around to keep CellRendererComponent and lazy render the rest of the data?
I know this is late, but for anyone else (like me) arriving here. React Native VirtualizedList calls CellRendererComponent like so:
<CellRendererComponent
{...this.props}
style={cellStyle}
onLayout={onLayout}>
{element}
{itemSeparator}
</CellRendererComponent>
Your custom CellRendererComponent has to implement at least the onLayout prop:
function MyCustomCellRenderer(props) {
return(
<View onLayout={props.onLayout}>
...etc
</View>
)
}

React Native : uri rendered multiple times in flatlist

I'm a newbie to react native. I try to add a flatlist to my app.
I hava an array of datas designed like that:
["https://hi.com//image.png", //uri
"hello",
"https://hi.com//image2.png",
"welcome",
"https://hi.com//image3.png",
"great",
../..
]
Problem is that my image shows up but the text in the right side is actually my uri stringyfied.
I think there's something wrong with the keyExtractor:
renderItem =({item}) => {
return(
<View style ={{flex:1, flexDirection:'row'}}>
<Image source ={{uri: item}}/>
<View style ={{flex:1, justifyContent: 'center'}}>
<Text>{item}</Text>
</View>
</View>
)
}
render() {
return (
<View style={styles.mainContainer}>
<FlatList
data= {this.state.dataSource}
keyExtractor={(item,index) => index.toString()}
renderItem= {this.renderItem}
/>
</View>
);
}
your renderItem function loops thru every single element of array, not sure its the best option for your kind of data, maybe try to use something like this instead
const data = [{uri: 'https://link.io', text: 'hello'},{uri: 'http://anotherlink.co', text: 'bye'}]
then inside your renderItem fuction pass data:
<Image source ={{uri: item.uri}}/>
<Text>{item.text}</Text>
on the other hand if your you need to keep flat array, maybe be write some function with modulo of deviding index by 2 and from there get what goes where, but not sure why would you need that apart maybe from codewars challange ;)
good luck, hope this helps
your flatlist render item is trying to access the item in a loop.so everytime it gets looped,you are passing item to Image and item to Text.As #wijuwiju suggested,that is the best way to implement it.try to maintain keys to your data.Then your flatlist will render properly.
Store Item Like this:
profilePicture: 'https://picsum.photos/200',
Set Image Source Like this:
<Image source={{uri:profilePicture}}/>

FlatList calls `onEndReached` when it's rendered

Here is render() function for my simple category list page.
Recently I added pagination for my FlatList View so when the user scrolls to the bottom, onEndReached is called in a certain point(onEndReachedThreshold value length from the bottom), and it will fetch the next categories and concatenate the categories props.
But my problem is onEndReached is called when render() is called In other words, FlatList's onEndReached is triggered before it reach the bottom.
Am I putting wrong value for onEndReachedThreshold? Do you see any problem?
return (
<View style={{ flex:1 }}>
<FlatList
data={this.props.categories}
renderItem={this._renderItem}
keyExtractor={this._keyExtractor}
numColumns={2}
style={{flex: 1, flexDirection: 'row'}}
contentContainerStyle={{justifyContent: 'center'}}
refreshControl={
<RefreshControl
refreshing = {this.state.refreshing}
onRefresh = {()=>this._onRefresh()}
/>
}
// curent value for debug is 0.5
onEndReachedThreshold={0.5} // Tried 0, 0.01, 0.1, 0.7, 50, 100, 700
onEndReached = {({distanceFromEnd})=>{ // problem
console.log(distanceFromEnd) // 607, 878
console.log('reached'); // once, and if I scroll about 14% of the screen,
//it prints reached AGAIN.
this._onEndReachedThreshold()
}}
/>
</View>
)
UPDATE I fetch this.props.categories data here
componentWillMount() {
if(this.props.token) {
this.props.loadCategoryAll(this.props.token);
}
}
Try to implement onMomentumScrollBegin on FlatList :
constructor(props) {
super(props);
this.onEndReachedCalledDuringMomentum = true;
}
...
<FlatList
...
onEndReached={this.onEndReached.bind(this)}
onEndReachedThreshold={0.5}
onMomentumScrollBegin={() => { this.onEndReachedCalledDuringMomentum = false; }}
/>
and modify your onEndReached
onEndReached = ({ distanceFromEnd }) => {
if(!this.onEndReachedCalledDuringMomentum){
this.fetchData();
this.onEndReachedCalledDuringMomentum = true;
}
}
I've got it working with
<Flatlist
...
onEndReached={({ distanceFromEnd }) => {
if (distanceFromEnd < 0) return;
...
}
...
/>
First check if the FlatList is inside a ScrollView or Content of native-base. Then take it outside of it
Actually you don't need to use Content or ScrollView, as FlatList has both ListFooterComponent and ListHeaderComponent.
Though it is not recommended, if you really need to use Flatlist inside ScrollView, then take a look at this answer: https://stackoverflow.com/a/57603742/6170191
After hours of trying different approaches I got it to work by wrapping the Flatlist with a View of fixed height and flex:1.
With this settings, I was able to get onEndReached called once and only after I scroll near the bottom. Here's my code sample:
render() {
const {height} = Dimensions.get('window');
return (
<View style={{flex:1, height:height}}>
<FlatList
data={this.props.trips_uniques}
refreshing={this.props.tripsLoading}
onRefresh={()=> this.props.getTripsWatcher()}
onEndReached={()=>this.props.getMoreTripsWatcher()}
onEndReachedThreshold={0.5}
renderItem={({item}) => (
<View style={Style.card}>
...
...
</View>
)}
keyExtractor={item => item.trip_id}
/>
</View>
)
}
My onEndReached() function just calls the API and updates my data. It doesn't do any calculations with regards to distance to bottom or threshold
Most of the times, this error is caused because of an incorrect use of onEndReachedThreashold, which also depends of the number of items you are rendering (more items, more scroll size).
Try to follow this logic:
If 10 items cover your screen, and you are rendering 20 items on each scroll, then set onEndReachedThreashold to 0.8.
If 2 or 3 items cover your screen, and you are rendering 10 items on each scroll, then set onEndReachedThreashold to 0.5.
Also, use initialNumToRender = numItems. For some reason, using this FlatList prop helps to reduce the chance of multiple onEndReached calls.
Just play with onEndReachedThreashold value.
Other times, this error is produced because of nesting scroll views. Do not put your FlatList inside of a ScrollView. Instead, take use of the FlatList header and footer props.
For both solutions, I suggest to set the FlatList style and contentContainerStyle to { flexGrow: 1 }.
Remove every Scrollable View inside your FlatList
If you want to show 3 or 4 records and want to load the next data just when you reach the end. Set onEndReachedThreshold to 0 or 0.1.
Maybe You can bypass this FlatList bug by incrementing your page before doing async call, and then you will fetch data on every onEndReached fiers and not get errors about duplicate keys
(as of NOV19)
Keep flatlist as the only component inside of a single view
Set style of that single view from dimensions like
{{flex: 1, height: Dimensions.get('window').height}}
If FlatList is on another FlatList or ScrollView the onEndReached call immediately when rendered component to resolve that problem doesn't wrap FlatList with another.
A bit late but I just ran into this issue and I fixed it by passing to my <FlatList/> the initialNumToRender prop. This prop is 10 by default so if you don't set it and your screen shows more than 10 items on the initial render, it is going to trigger onEndReached since it has passed the 10th element.
initialNumToRender should probably be the same as the amount of elements you fetch per page.
I have a <FlatList> (from react-native) inside an <Overlay> (from react-native-elements.) I have the problem of onEndReached being executed as soon as the component is rendered for the 1st time and before the user does anything.
The problem was resolved by using <Modal> (from react-native), instead of <Overlay>.
If you are using hooks, here you can find the hook version of #Ilario answer:
const onEndReachedCalledDuringMomentum = useRef(true)
onEndReachedHandler = ({ distanceFromEnd }) => {
if(!onEndReachedCalledDuringMomentum.current){
fetchData()
onEndReachedCalledDuringMomentum.current = true
}
}
<FlatList
...
onEndReached={onEndReachedHandler}
onEndReachedThreshold={0.7}
onMomentumScrollBegin={() => { onEndReachedCalledDuringMomentum.current = false }}
/>
This simple solution worked for me. Note the "refreshing" state is controlled by an async API call in a useEffect hook to retrieve data for the FlatList.
const onEndReachedHandler = () => {
if (!refreshing) ...
}
<FlatList
...
data={mydata}
onEndReached={onEndReachedHandler}
onEndReachedThreshold={0.7}
refreshing={refreshing}
/>
I struggled around the whole day but the issue that I was getting is, I am using FlatList inside ScrollView. So, Remove Scrollview & then use Flatlist independently. This will solve my problem.
From my experience, you can simply utilize onEndReachedThreshold props in your FlatList or SectionList and pass a very very small number like 0.001 to it.
onEndReachedThreshold={0.001}
According to docs for FlatList, onEndReachedThreshold is units of length from the bottom in list items.
How far from the end (in units of visible length of the list) the
bottom edge of the list must be from the end of the content to trigger
the onEndReached callback. For example, a value of 0.5 will trigger
onEndReached when the end of the content is within half the visible
length of the list.
Thus, a very small value like 0.001 helps you to make sure that onEndReached is only gonna be called when the end of the content is within the very end of the visible length of the list.
Hope this helps :) Sorry for bad English.
The solution is simpler than anyone would think.
Just add an !isLoading condition for fetch calling. It works for me:
onEndReached={() => {
if (!isLoading) {
fetchProducts();
}
}}
And the full code with ScrollView and FlatList:
<ScrollView
horizontal={true}
showsVerticalScrollIndicator={false}
showsHorizontalScrollIndicator={false}
style={styles.mainContainer}>
<FlatList
ListHeaderComponent={TableHeader}
data={displayedProducts}
renderItem={(item: ListRenderItemInfo<IProduct>) => TableRow(item, showDeleteModal)}
keyExtractor={keyExtractor}
ListFooterComponent={<Loading loadingText={'Loading products...'} />}
onEndReached={() => {
if (!isLoading) {
fetchProducts();
}
}}
onEndReachedThreshold={0.8}
stickyHeaderIndices={[0]}
/>
</ScrollView>
I have solved it with using debounce from lodash. Firstly, I import debounce from 'lodash.debounce'. Then I use debounce for load more function with 500 ms interval
<Flatlist
onEndReached = {debounce(this._onLoadMore, 500)}
/>