Best method to optimize performance of FlatList Items - react-native

This a simple FlatList:
class Products ..
render() {
return (
<FlatList
renderItem={this._renderItem}
);
}
I want to create a list of items and navigate to Detail Page by onPress items.
Can Please tell me which method is better?
Method 1:
Insert navigate to Detail page in child component(CardProduct component) like this:
_renderItem = ({item}) => (
<CardProduct
id={item.id}
title={item.title}
/>
);
and in CardProduct component:
render() {
const { id,title } = this.props;
return (
<Card style={{flex:1}}>
<CardItem cardBody button onPress={() => this.props.navigation.navigate('Details',{productId:id})}>
...
);
}
Method 2:
Insert navigate to Detail page in current component(Products component) like this:
_onPressItem = (id: string) => {
this.props.navigation.navigate('Details',{productId:id});
};
_renderItem = ({item}) => (
<CardProduct
id={item.id}
title={item.title}
onPressItem={this._onPressItem}
/>
);
and in CardProduct component:
_onPress = () => {
this.props.onPressItem(this.props.id);
};
render() {
const { id,title } = this.props;
return (
<Card style={{flex:1}}>
<CardItem cardBody button onPress={this._onPress}>
...
);
}
I used to do the method 1, but I read this guide.

Short answer:
You should go for method2.
Explanation:
In method1 you are using an arrow function in CardItem's onPress, so everytime CardProduct is re-rendered a new reference of onPress is created, which forces CardItem to re-render, even if all the other props are staying the same. In method 2 you are binding the function to context, which won't force a re-rendering of the CardItem.
By the way, in general it is a good idea to prevent the usage of arrow functions in render().

One step for performance optimization in react-native flatlist, is using a stateless functional component for the renderItem. and you should always give each item a unique key.

Related

react-native : how to sort data before screen is drawn?

I call data by using useQuery and gql.
const SEE_ALL_FEED_ORDER = gql`
query seeAllFeedOrder {
seeAllFeedOrder {
id
name
avatar
directFeedNumber
}
}
`;
const { data: allFeedData, loading: allFeedDataLoading,
refetch: allFeedRefetch } = useQuery(SEE_ALL_FEED_ORDER);
I named result data as allFeedData as above.
And I need to sort this allFeedData before screen is shown up.
So I use useEffect and useState.
const [flatlistdata, setFlatlistdata] = useState([]);
useEffect(() => {
if (!allFeedDataLoading) {
setFlatlistdata(
[...allFeedData.seeAllFeedOrder].sort(function (a, b) {
return b.directFeedNumber - a.directFeedNumber;
})
);
}
}, []);
So if this query loading is finished, then I put sorted data to flatlistdata by using setFlatlistdata.
And with this flatlistdata, I run flatlist.
<FlatList
data={flatlistdata}
keyExtractor={(item) => item.id}
renderItem={RankRow}
refreshing={refreshing}
onRefresh={refresh}
/>
But when I click screen, undefined is not an object(evaluating 'allFeedData.seeAllFeedOrder' error comes.
which means I couldn't call allFeedData.
I think this might happen screen is drawn before data is sorted? is that right?
So I also give condition to Flatlist as below.
flatlistdata === [] ? (
<View>
<ActivityIndicator size={30}></ActivityIndicator>
</View>
) :
<FlatList
data={flatlistdata}
keyExtractor={(item) => item.id}
renderItem={RankRow}
refreshing={refreshing}
onRefresh={refresh}
/> }
or
allFeedDataLoading ? (
<View>
<ActivityIndicator size={30}></ActivityIndicator>
</View>
) :
<FlatList
data={flatlistdata}
keyExtractor={(item) => item.id}
renderItem={RankRow}
refreshing={refreshing}
onRefresh={refresh}
/> }
I intended to draw ActivityIndicator screen first before data is sorted and put to flatlistdata and then proper screen show up, but it throws same error.
What is the problem? and how can I fix this ?
So I use useEffect and useState.
why would you need that? the sorted data is derived state that doesn't need to be managed separately:
const { data: allFeedData } = useQuery(SEE_ALL_FEED_ORDER);
const sortedData = allFeedData ? [...allFeedData.seeAllFeedOrder].sort(...) : []
then just pass sortedData to the FlatList. If it turns out that sorting is slow or that referential identity is important, you can wrap the sorting in useMemo. I also have a blog post on this topic: https://tkdodo.eu/blog/dont-over-use-state

React Native - Focus a TextInput (Inside custom Component) that is inside of a Flat List element

I'm a React Native beginner and I'm working with a Flat List and custom Rows. Inside of each custom row, I have elements like Text, TextInput, and Button. The problem is that I need to press one of these buttons that enables and triggers a focus to the TextInput.
I tried implementing that with refs but everything freezes so I don't know how to do that correctly.
My constructor
class EditProfileScreen extends React.Component {
constructor(props) {
super(props);
this._rowRefs = {}
}
My FlatList and MyCustomRow
_renderItem = ({item, index}) => {
return (
<MyCustomRow
fieldname={item.fieldname}
value={item.value}
isEditable={item.isEditable}
editFieldHandler={ this.editFieldHandler }
allowsEdition={item.allowsEdition}
ref={ref => { this._rowRefs[index] = ref}}
/>
)
}
_keyExtractor = (item, index) => index.toString();
render() {
return (
<View style={styles.mainContainer}>
<FlatList
ref={this.flatListRef}
style={styles.flatList}
data={this.state.fields}
renderItem={this._renderItem}
keyExtractor= {this._keyExtractor}
extraData={this.state.refresh}
/>
</View>
);
}
Trying to print the refs:
enableField(name) {
console.log('Printing refs...')
console.log(this._rowRefs) // the Button pressed freezes here
}
I expect to see what's inside of _rowRefs, but instead of that, the button just gets frozen and I never see that result in the console
Try to give the key for each ref (not index)
ref={ref => { this._rowRefs['key1'] = ref}}
and invoke focus action like this:
this._rowRefs['key1'].focus()
Can you try the above code after removing
console.log(this._rowRefs)

React-Native re-render Flatlist

I'm having trouble keeping the data in my Flatlist after coming back from another page. My scenario is as follows:
User goes to homepage and scrolls through 20 items
User clicks their profile tab changing page using react-native-router-flux
User clicks the home tab taking them back to the list however the list re-renders and starts from the top.
How can I stop this re-rendering and fetching the same data again?
componentDidMount() {
this.makeRemoteRequest();
}
makeRemoteReuest gets my data from firebase in batches of 5 and sets data: []
data: [...this.state.data, ...results]
I've tried using the below but not sure if this is correct, when i navigate away and back the data re-renders. I want to keep the data so the page will be exactly the same as when it was left.
shouldComponentUpdate(nextProps, nextState) {
if (JSON.stringify(this.state.data) !== JSON.stringify(nextState.data)) {
return true;
}
return false;
}
My flatlist:
<View>
<FlatList
scrollsToTop={false}
ref={(ref) => { this.flatListRef = ref; }}
showsHorizontalScrollIndicator={false}
onScroll={this.handleScroll}
data={this.state.data}
keyExtractor={item => item.key}
ListFooterComponent={this.renderFooter()}
onRefresh={this.handleRefresh}
refreshing={this.state.newRefresh}
onEndReached={this.handleEndRefresh}
onEndReachedThreshold={0.05}
getItemLayout={this.getItemLayout}
renderItem={this.renderItem}
/>
{this.state.refreshAvailable ? this.renderRefreshButton() : null}
</View>
Thanks for any help!
Coded long back for the dumb project, maybe this can help you
The View: used onLayout Prop for getting the y-axis
<ScrollView
ref={(ref) => this.scrollTo = ref}
contentContainerStyle={{margin:5,}}
>
<Card onLayout={(event) => this._findHeight(event.nativeEvent.layout, 'personal')}>
<Personal review={true}/>
</Card>
</ScrollView>
The Function: stored the y-axis; here i have used realm db
_findHeight = (e, name) => {
const {x, y, width, height} = e;
this.realm.write(() => {
this.realm.create('yLocation',{names:name,yaxis:y}) :
});
}
The AutoScroll Method: here i have used scrollTo method from ScrollView you can use any method using their ref
_scrollTo = (y) => {
this.scrollTo.scrollTo({x:0,y:y,animated:true});
}
Note : Call _scrollTo method in componentDidMount

Pass parameters with props in react native

I want to pass the clicked searchItem's id from my component. My code is as follows.
const searchResultList = props => (
<View style={styles.container}>
<ScrollView keyboardShouldPersistTaps="always">
{props.itemList.map(item => (
<SearchResultListItem
key={item.id}
imageSource={item.workoutImage}
mainText={item.workoutName}
subText={item.length + " | " + item.difficulty}
onItemPressed={props.onItemPressed}
/>
))}
</ScrollView>
</View>
);
When, onItemPressed, i want to pass the item.id with the prop.onItemPressed. This is my screen.
<SearchResultList
itemList={this.props.searchedWorkouts}
onItemPressed={id => alert(item.id)} //this.onLoadWorkoutDetailView()}
/>
How can I achieve this? I want the clicked itemId to my main screen.
You need to make an addition function. Straightforward it will be like this:
...
onItemPressed={() => props.onItemPressed(item.id)}
...
But, it`s not a good solution from performance point of view.
The better way is to make a method and bind all items to this method.
class SearchResultList extends React.PureComponent {
constructor(props) {
super(props);
const {itemsList} = props;
this._onPressHandlers = {};
for (let {id} for itemsList) {
this._onPressHandlers[id] = this._onItemPress.bind(this, id);
}
}
_onItemPress(id) {
return this.props.onItemPressed(id);
}
render() {
return (
<View style={styles.container}>
<ScrollView keyboardShouldPersistTaps="always">
{props.itemList.map(item => (
<SearchResultListItem
key={item.id}
imageSource={item.workoutImage}
mainText={item.workoutName}
subText={item.length + " | " + item.difficulty}
onItemPressed={this._onPressHandlers[item.id]}
/>
))}
</ScrollView>
</View>
);
}
}
In this case the handlers will not be generated on each render of list and will not cause re-render of SearchResultListItem component.
Obviously, if you expect changes of the itemsList prop, you will need to rebind it on componendWillReceiveProps method.

FlatList items won't respond to touches while FlatList is loading new items

My list component looks like this:
<List>
<FlatList
data={films}
renderItem={({ item }) => (
<FilmListItem
filmSummary={item}
navigateToDetailsScreen={navigateToDetailsScreen}
/>
)}
keyExtractor={() => cuid.slug()}
ListHeaderComponent={<Header totalResults={totalResults} />}
ListFooterComponent={<Footer />}
onEndReached={() => dispatchFetchPage()}
/>
</List>
The FilmListItem render function looks like this:
render() {
const { filmSummary } = this.props
return (
<TouchableOpacity onPress={this.onPress}>
<ListItem title={filmSummary.Title} subtitle={filmSummary.Year} />
</TouchableOpacity>
)
}
The FlatList loads a screen-full of items, then continues loading three more screen-fulls. But the TouchableOpacity items already rendered won't respond to touches until the FlatList has finished loading the additional pages.
Once the FlatList has finished loading, items respond to touches, but not before. If I scroll the list to the bottom and the FlatList continues loading more items, the visible items become unresponsive again.
Am I missing something?
It seems related to performance issue. My suggestions:
Using pure component for your render item function. It will shallow compare, so it won't re-render that easily. performance improved.
Don't use the anonymous function on your props. move it out outside render function. So every time your component re-render, that function won't re-create.
add initialNumToRender={number} prop to Flatlist. It will show only those component which visible on the screen.
example code:
// 1. make sure your FilmListItem is pure component!!
class FilmListItem extends React.PureComponent {
render() {
// ....
}
}
// 2. Move out all anonymouse function
_renderItem = ({ item }) => (
<FilmListItem
filmSummary={item}
navigateToDetailsScreen={navigateToDetailsScreen}
/>
);
_keyExtractor = () => cuid.slug();
// render of your main component
render() {
return (
<List>
<FlatList
initialNumToRender={number} // 3. try add initialNumToRender
data={films}
renderItem={this._renderItem}
keyExtractor={this._keyExtractor}
ListHeaderComponent={<Header totalResults={totalResults} />}
ListFooterComponent={<Footer />}
onEndReached={dispatchFetchPage} // no need anonymouse function
/>
</List>
);
}
You can take a look similar performance problem in here:
https://github.com/facebook/react-native/issues/13649