简体   繁体   中英

FlatList re-rendering every time i type something in TextInput

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.

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 (eg 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

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.

@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.

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);}}
    />
  );

}

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM