Reputation: 121
I don't think this is a bug, more like I'm doing something wrong but here it goes. I'm building a Chat Component for my application, it's very simple, I type something press send button adds to the list and displays. Days ago I found that if I started typing big and quick messages my JS FPS would drop to like 10, 5 even 1 and I found out today thanks to implementing a data/time display for the messages that each time I type something in the TextInput and run onChangeText event and change the state of "text" it re-renders ALL the items inside my FlatList
Here is my code:
Chat Component:
<FlatList
key={'chat'}
ref={(ref) => {flatlistRef = ref}}
style={styles.flatlist_main}
data={this.state.items}
extraData={this.state}
renderItem={this.renderItem}
keyExtractor={this.keyExtractor}
onContentSizeChange={(contentWidth, contentHeight) => {
flatlistRef.scrollToEnd({animated: true});
}}
onLayout={() => {flatlistRef.scrollToEnd({animated: true})}}
/>
onChangedText Function
onFooterTextChanged = (text) => {
this.setState({
text: text,
options_expanded: false,
});
};
onSendMessage Button Function
onSendButtonPressed = () => {
if(this.state.text !== null && this.state.text !== "") {
this.setState({
items: [
...this.state.items,
{
id: moment().valueOf(),
text: this.state.text,
date: moment().valueOf(),
user: {
id: globals.user_id_value,
avatar: globals.user_photo,
}
}
],
text: "",
options_expanded: true,
});
}
};
renderItem Function for FlatList
renderItem = (item) => {
const data = item.item;
const renderAvatar = this.renderAvatar(item);
const renderTime = this.renderTime(item);
if('text' in data){
return(
<ChatTextItem
keyy={data.id}
self={globals.user_id_value === data.user.id}
text={data.text}
user={data.user}
renderAvatar={renderAvatar}
sameUser={!renderAvatar}
renderTime={renderTime}
time={this.getTime(data.date)}
/>
)
} else if('image' in data) {
return(
<ChatImageItem
keyy={data.id}
self={globals.user_id_value === data.user.id}
image={data.image}
renderAvatar={renderAvatar}
sameUser={!renderAvatar}
renderTime={renderTime}
time={this.getTime(data.date)}
/>
)
}
};
Constructor if it helps
constructor(props){
super(props);
this.state = {
isLoading: false,
options_expanded: true,
text: "",
image: "",
items: [],
};
}
and I'm user PureComponent btw.
Edit #1: Console after typing like a madman in the TextInput
It re-rendered 28 times for the 28 letters I typed and it does that * the number of items in the list already
Edit #2: Changes Made to the JS file
Changed FlatList extraData option
<FlatList
key={'chat'}
ref={(ref) => {flatlistRef = ref}}
style={styles.flatlist_main}
data={this.state.items}
extraData={this.state.refresh}
renderItem={this.renderItem}
keyExtractor={this.keyExtractor}
onContentSizeChange={(contentWidth, contentHeight) => {
flatlistRef.scrollToEnd({animated: true});
}}
onLayout={() => {flatlistRef.scrollToEnd({animated: true})}}
/>
and changed the constructor to add refresh state
constructor(props){
super(props);
this.state = {
isLoading: false,
options_expanded: true,
text: "",
image: "",
items: [],
refresh: false,
};
}
Issue still persists
Edit: #3 Finally found the issue
Works
<FlatList
key={'chat'}
ref={(ref) => {flatlistRef = ref}}
style={styles.flatlist_main}
data={this.state.items}
extraData={this.state.refresh}
renderItem={this.renderItem}
keyExtractor={this.keyExtractor}
/>
Doesn't Work
<FlatList
key={'chat'}
ref={(ref) => {flatlistRef = ref}}
style={styles.flatlist_main}
data={this.state.items}
extraData={this.state.refresh}
renderItem={this.renderItem}
keyExtractor={this.keyExtractor}
onContentSizeChange={(contentWidth, contentHeight) => {
flatlistRef.scrollToEnd({animated: true});
}}
onLayout={() => {flatlistRef.scrollToEnd({animated: true})}}
/>
Any Ideas Why? Please!
If someone could help me find a solution to my problem I would appreciate it.
Upvotes: 6
Views: 4398
Reputation: 57
It is due to the hooks of text input present in the same component containing flatlist.
Separate whole text input from the component
//Component contains flat list
const ParentComponent = ()=>{
return(
<FlatList/>
<CustomTextInput/>
);
}
//Textinput separate component
const CustomTextInput = ()=>{
const [value,setValue] = useState(null);
return (
<TextInput
value = {value}
onChangeText={(data)=>{setValue(data);}}
/>
);
}
Upvotes: 0
Reputation: 1
@Guy hope you have found solution.
But for new devs. As per my struggle in same situation i found that:- For any use case when Flatlist and TextInput in same component then please try to use Flatlist in separate component (PureComponent) and textInput in separate component. Re-rendering will not happen.
Or you can use separate component for flatlist only, it will not re-render each time when state change.
Upvotes: 0
Reputation: 121
Apparently, if I remove onLayout
and onContentSizeChanged
events from my FlatList
it stops rendering on any state changes and only the state provided in extraData, still doesn't solve my issue and it only made performance worse surprisingly.
Thanks to everyone that helped to solve this.
Upvotes: 0
Reputation: 3671
It looks like it's due to your extraData
receiving the whole state
This is a PureComponent which means that it will not re-render if props remain shallow- equal. Make sure that everything your renderItem function depends on is passed as a prop (e.g. extraData) that is not === after updates, otherwise your UI may not update on changes. This includes the data prop and parent component state
So this is the expected behavior since you are updating your state every time the user types in your input field.
Also, FlatList should render (or rerender) only the items visible in the screen
In order to constrain memory and enable smooth scrolling, content is rendered asynchronously offscreen. This means it's possible to scroll faster than the fill rate and momentarily see blank content. This is a tradeoff that can be adjusted to suit the needs of each application, and we are working on improving it behind the scenes.
Try fixing your extraData
. Something like extraData={this.state.refresh}
where this.state.refresh
is updated when the user presses send
button.
More details here: https://facebook.github.io/react-native/docs/flatlist.html
Hope it helps
Upvotes: 3
Reputation: 1590
I don't think it is bug, This is what we need it to your chat application right. As you were updating the state for each onPress(onSendButtonPressed) to show the new message in the list. So it re-renders the list. May be what i suggest is, you just show the recent messages in your flatlist. ie last 3 or 4 days message in the flatList and load more when the user goes backward beyond 4 days. This will help you to improve the performance of your chat application.
Upvotes: -1