Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FlatList] FlatList unmounts its header component continuously #13602

Closed
navid94 opened this issue Apr 20, 2017 · 14 comments
Closed

[FlatList] FlatList unmounts its header component continuously #13602

navid94 opened this issue Apr 20, 2017 · 14 comments
Labels
Resolution: Locked This issue was locked by the bot.

Comments

@navid94
Copy link

navid94 commented Apr 20, 2017

Description

I have a FlatList with ListHeaderComponent, that is composed with some pickers, when you scroll down or up the header component is being mounted and unmounted continuously, causing the header status to be reset, so pickers will lose their current value.

Reproduction Steps and Sample Code

My implementation:

constructor(props) {
        super(props);
        this.state = {
            refreshing: false,
            data: []
        };
    }

getItemsData(jobList, start, count) {
        var data = [];
        for (var i = start; i < count + start && i < jobList.length; i++)
        {
            data.push(jobList[i]);
        }
        return data;
    }

onEndReached() {
        this.setState({
            data: this.state.data.concat(this.getItemsData(this.props.jobs.jobList, this.state.data.length, 5))
        });

    }

shouldItemUpdate(props, nextProps) {
        if (_.isEqual(props, nextProps))
        {
            return false;
        }
        else
        {
            return true;
        }
    }

componentWillReceiveProps(nextProps) {
        if (nextProps.jobs.jobList.length > 0 && !nextProps.jobs.isFetchingJobList)
        {
            this.setState({
                data: this.getItemsData(nextProps.jobs.jobList, 0, 10)
            });
        }
    }

render() {
        return (
            <View>
                <FlatList
                    ListHeaderComponent={() => <JobListHeader onApplyFilters={this.onApplyFilters.bind(this)}/> }
                    data={this.state.data}
                    renderItem={this.renderItem.bind(this)}
                    keyExtractor={this.keyExtractor}
                    numColumns={1}
                    onEndReached={this.onEndReached.bind(this)}
                    initialNumToRender={5}
                    maxToRenderPerBatch={4}
                    shouldItemUpdate={this.shouldItemUpdate.bind(this)}
                    onEndReachedThreshold={6}
                    refreshing={this.state.refreshing}
                    onRefresh={this.onRefresh.bind(this)}
                    ref="listRef"
                />
            </View>
        );
    }

JobListHeader render only

It seems that onEndReached triggers unmount and mount.

Additional Information

  • React Native version: 0.43.2
  • Platform: Android
  • Development Operating System: Linux
  • Dev tools: Android Studio 2.3, Android SDK 23
@navid94
Copy link
Author

navid94 commented Apr 21, 2017

I think this is due to the fact that FlatList treats the ListHeaderComponent like a normal Item, but my header has its state because has a search box and some filters pickers. I need those components to be inside of the scrollView of the FlatList, otherwise their position will be fixed on top, while scrolling the list.

@chechunhsu
Copy link

+1

@hramos hramos changed the title FlatList unmounts its header component continuously [FlatList] FlatList unmounts its header component continuously Apr 25, 2017
@gabrielgomesferraz
Copy link

+1

1 similar comment
@cbjs
Copy link

cbjs commented Apr 29, 2017

+1

@alexanderjarvis
Copy link

alexanderjarvis commented May 9, 2017

I've got an example where this is much worse than originally stated in this issue. Without scrolling, the flatlist header can continuously unmount if there is data loading for other parts of the flatlist (so prop changes for other sections).

@gregblass
Copy link

+1

@MovingGifts
Copy link

@sahrens since you worked on FlatList, is there any solution to the unmounting header?

@sahrens
Copy link
Contributor

sahrens commented May 10, 2017

This is a little nuanced...

tl;dr: always pre-bind functions you pass as props so you don't re-create the function on every render.

So ListHeaderComponent is of type ReactClass, which means that when you pass in a function like you're doing, it's not just called as a plain function (like renderItem), but as a stateless, functional react component (hence the name...*Component). This means that if you re-create and re-bind the function in render like you are doing (e.g. any time you call setState in onEndReached), React will treat it as a new react component class and will destroy and recreate the instance every time, just as if you were switching back and forth between <Text> and <View>.

I would recommend turning that function into an autobound method like so:

_headerComponent = () => <JobListHeader onApplyFilters={this.onApplyFilters.bind(this)} />;

then when you set the prop, it will always be the same thing and react will update the instance rather than re-creating from scratch:

ListHeaderComponent={this._headerComponent}

If the auto-binding initializer syntax doesn't work for you for some reason, you can bind it in the constructor instead.

I also recommend auto-binding for all your other props as well, like renderItem, because it will skip unnecessary work and wasted memory with constantly re-binding the same functions, and will skip unncessary re-renders when doing the PureComponent shallow equality checks.

Couple other notes:

  1. shouldItemUpdate was deprecated so it no longer does anything.
  2. Don't use string refs, use functional refs.

@sahrens sahrens closed this as completed May 10, 2017
@MovingGifts
Copy link

Thank you for the quick response. Just wondering, how would you bind it in the constructor instead?

@sahrens
Copy link
Contributor

sahrens commented May 10, 2017

constructor(props, context) {
  super(props, context);
  this._headerComponent = this._headerComponent.bind(this);
  ...
}

@MovingGifts
Copy link

Thank you so much @sahrens

@cbjs
Copy link

cbjs commented May 11, 2017

thanks @sahrens

@alexanderjarvis
Copy link

alexanderjarvis commented May 11, 2017

thanks @sahrens

Something worth mentioning though is you say it's of type ReactClass but it's not, its ReactClass<any> | React.Element<any> (https://github.com/facebook/react-native/blob/master/Libraries/Lists/FlatList.js#L89).

I'm worried that other people will use this class and won't realise their header is un-mounting constantly.

...and shouldn't it be () => ReactClass<any> | React.Element<any> ?

@jsina
Copy link

jsina commented May 7, 2018

@xavier-villelegier unfortunately in my case the ListHeaderComponent doesn't update. I have an component that show the calendar with next and previous day button for ListHeaderComponent, I've followed the workaround but after the state on SearchScreen changed, the ListHeaderComponent won't re-render.

export default class SearchScreen extends Component {
state = {
    currentDate: new Date(),
    timeText: gregorianToJalai(new Date()),
    skip: this.props.list.skip,
  };

previousDateOnPress = () => {
    const date = this.state.currentDate;
    date.setDate(date.getDate() - 1);
    this.setState({
      currentDate: date,
      timeText: gregorianToJalai(date),
    });
  };

  nextDateOnPress = () => {
    const date = this.state.currentDate;
    date.setDate(date.getDate() + 1);
    this.setState({
      currentDate: date,
      timeText: gregorianToJalai(date),
    });
  };
...
render() {
    const { loading, data, refreshing } = this.props.list;
    return (
      <View>
        <FlatListCard
          goToClubInfoPage={this.goToClubInfoPage}
         ...
          timeText={this.state.timeText}
          previousDateOnPress={this.previousDateOnPress}
          nextDateOnPress={this.nextDateOnPress}
        />
      </View>
    );
  }
export default class FlatListCard extends Component {
constructor(props) {
    super(props);
    this.renderHeader = this.renderHeader.bind(this);
  }

renderHeader = () => (
    <FilterCalendar
      timeText={this.props.timeText}
      previousDateOnPress={this.props.previousDateOnPress}
      nextDateOnPress={this.props.nextDateOnPress}
    />
  );
render() {
    return (
      <FlatList
        ...
        ListHeaderComponent={this.renderHeader}
      />
    );
  }

@facebook facebook locked as resolved and limited conversation to collaborators May 24, 2018
@react-native-bot react-native-bot added the Resolution: Locked This issue was locked by the bot. label Jul 18, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Resolution: Locked This issue was locked by the bot.
Projects
None yet
Development

No branches or pull requests

10 participants