react-native: [FlatList] Sometimes FlatList rows are rendered, but not displayed until scroll. RN 0.43

Description

When using FlatList, rows are not displayed though renderItem is called. Rows appear immediately when scroll is triggered on the list.

Reproduction Steps and Sample Code

export default class List extends Component {
  renderItem({item}) {
    return (
      <Text>{item}</Text>
    );
  }

  render() {
    return (
      <FlatList
        style={{
          flex: 1,
          backgroundColor: 'white',
        }}
        data={[1, 2]}
        renderItem={this.renderItem}
      />
    );
  }
}

Solution

FlatList should always display its rendered items.

Additional Information

  • React Native version: 0.43.0
  • Platform: iOS
  • Development Operating System: MacOS
  • Dev tools: Xcode

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 36
  • Comments: 78 (27 by maintainers)

Commits related to this issue

Most upvoted comments

Does setting removeClippedSubviews={false} fix the issue? Sounds like the issue might be related to the navigator which would explain why we don’t see this problem at Facebook. cc @shergin

Hi mine fixed by giving style={{ backgroundColor: 'white' }} to flat list

Why the issue is closed?! 😃 Why removeClippedSubviews={false} is considered as working solution?

When you set removeClippedSubviews={false} it renders ALL cells even those out of screen. Now imagine feed with 50-100 images. It will just blow up Android and probably any iOS app.

@sahrens removeClippedSubviews={false} works for us for now.

We are using Navigator for a tab based app.

  1. Navigate to the flatlist scene. List shows.
  2. Navigate to another tab.
  3. Make a change which affects the flatlist scene.
  4. Navigate to the already mounted flatlist scene.
  5. Flatlist shows as empty.
  6. Touch component and the items are made visible.

Still with the FlatList in React Native 0.54. It started happening all of a sudden and it’s actually annoying.

Here is a gif may-25-2017 10-57-37

Inspired by the comments in this thread from Kiarash-Z and douglasjunior, my fix to this solution was basically:

ListFooterComponent={() => {
  return <View style={{backgroundColor: 'transparent', height: 1}} />
}}

I could not use a ActivityIndicator but it seems RN needs new content to be rendered, even an “invisible” view (Note, with height of zero the bug still existed).

I am having this issue still even with removeClippedSubviews={false}. Any additional solutions here?

We’re also seeing this issue. Looks like it could be something to do with the FlatList being in a navigator on iOS. Managed to reproduce it in a standalone project. You’ll have to excuse the code it’s a bit rough.

react-native init and add the following code as your index.ios.js. Once launched, tap on A. Tap remove. Tap back. Then you’ll need to scroll to get FlatList to re-render.

We found this on RN0.43.1 iOS simulator, iPad but not android. Seems to work as expected on android

/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 * @flow
 */

import React, { Component } from 'react';
import { AppRegistry, StyleSheet, Text, View, FlatList, TouchableOpacity, Navigator } from 'react-native';

let data = [ { id: 'a' }, { id: 'b' }, { id: 'c' } ]
let listeners = []

class List extends Component {
  constructor (props) {
    super(props)
    this.state = { data: Array.from(data) }
  }

  componentDidMount () {
    listeners.push(() => { this.setState({ data: data }) })
  }

  render () {
    return (
      <FlatList
        data={this.state.data}
        keyExtractor={(item) => item.id}
        renderItem={({item}) => (
          <TouchableOpacity onPress={() => this.props.navigator.push({ id: 'r2', item: item })}>
            <View>
              <Text>{item.id}</Text>
              <Text>{item.id}</Text>
              <Text>{item.id}</Text>
              <Text>{item.id}</Text>
            </View>
          </TouchableOpacity>
        )}
      />
    )
  }
}

export default class AwesomeProject extends Component {

  renderScene (route, navigator) {
    if (route.id === 'r1') {
      return (<List navigator={navigator} />)
    } else if (route.id === 'r2') {
      return (
        <View>
          <Text>{route.item.id}</Text>
          <TouchableOpacity onPress={() => {
            data = [data[0], data[2]]
            listeners.forEach((l) => { l() })
          }}>
            <Text>Remove</Text>
          </TouchableOpacity>
          <TouchableOpacity onPress={() => navigator.pop()}>
            <Text>Back</Text>
          </TouchableOpacity>
        </View>
      )
    }
  }

  render() {
    return (
      <View style={[StyleSheet.absoluteFill, { paddingTop: 50 }]}>
        <Navigator initialRoute={{ id: 'r1' }} renderScene={this.renderScene.bind(this)} />
      </View>
    );
  }
}

AppRegistry.registerComponent('AwesomeProject', () => AwesomeProject);

My solution does not change the removeClippedSubviews.

In my case, I do infinite scrolling loading 100 records per time, so I changed the values of initialNumToRender, maxToRenderPerBatch and windowSize to 50.

<FlatList
    ...
    initialNumToRender={50}
    maxToRenderPerBatch={50}
    windowSize={50}
/>

After loading more than 600 items, if I scroll up quickly, some items appear blank and are quickly loaded again, that was satisfactory to me. (In dev mode it looks pretty bad, but in release mode is good)

flatlist

"react-native": "0.48.4"

removeClippedSubviews is now off by default in master.

This is still a problem with 0.50.3. removeClippedSubviews don’t seem to have any effect. Can we reopen this please?

@sahrens Could you explain why removeClippedSubviews fixes the issue? Is setting removeClippedSubviews as false an optimal solution or is it just a temporary quick fix?

Because I am using other RN libraries like react-native-sortable-listview that suffers from the similar issue (rows not appearing until initial scroll) even though it uses ListView, not FlatView. Thus, I guess it’s not only limited to FlatList, but also affect other types of scroll views as well. When I manually set removeClippedSubviews={false}, it fixes the issue, but also causes some unwanted side effects.

@BlakeSimpson this is good, but again it is not viable solution in the long run because it is basically a hack.

Why is this closed again, if this is not resolved 👎

When fetching the data, render an ActivityIndicator as ListFooterComponent. Example here.

this is still happening in 44 with removeClippedSubviews={false} set

initialNumToRender & removeClippedSubviews doesn’t seem like a viable answer here, because it removes all the optimisations when you have a huge list full of pictures.

@Thomas101 @sahrens I am using react-navigation for your information.

Found a working Hack for this.

render() {
       setTimeout(() => {
            this.sectionList && this.sectionList.recordInteraction(); 
        }, 50);
        return (
            <SectionList ref={(view) => { this.sectionList = view }} />
        )
} 

Super weird, when I edited getItemLayout to not match my actual item component, it rendered immediately again for me…

getItemLayout={(data, index) => (
    {
        length: itemWidth - 1, // why does this work
        offset: itemWidth * index,
        index
    }
)}

Edit: Or not.

@Kiarash-Z fix works and greatly improves performance. But why tho?

I have been fighting the problem of a FlatList with horizontal={true} and initialScrollIndex momentarily rendering the content, then blanking out until the view is scrolled again. The FlatList is rendered within a react-navigation StackNavigator.

I found the following combination to work for me:

  • calculating itemLayout in which length is equal to the window width
  • explicitly defining a width attribute on the top-level component within the renderitem.

This is my working component; I hope it’s helpful:

export default class DetailList extends PureComponent {
  render() {
    return (
      <FlatList
        contentContainerStyle={styles.list}
        data={this.props.shirts}
        getItemLayout={this._itemLayout.bind(this)}
        horizontal={true}
        initialScrollIndex={this.props.showIndex}
        keyExtractor={this._keyExtractor}
        pagingEnabled={true}
        renderItem={this._renderItem.bind(this)}
        showsHorizontalScrollIndicator={false}
      />
    );
  }

  _itemLayout(data, index) {
    const width = this._itemWidth()

    return {
      length: width,
      offset: width * index,
      index,
    };
  }

  _itemWidth() {
    const { width } = Dimensions.get("window");
    return width;
  }

  _keyExtractor(item, index) {
    return item.id;
  }

  _renderItem({index, item: rowData}) {
    return (
      <DetailItem
        index={index}
        shirt={rowData}
        width={this._itemWidth()}
      />
    );
  }
}

I’m still experiencing this issue. removeClippedSubviews={false} solved the problem for now.

removeClippedSubviews does not work for me, although I’m using a very common FlatList, as below:

<FlatList
   scrollEventThrottle={200}
   onScroll={e => true}
   ref={ref => this.listRef = ref}
   data={this.state.messageList}
   keyExtractor={item => item.messageId}
   removeClippedSubviews={false}
   renderItem={({item, index}) => {
      return (
          <MessageItem item={item}/>
      )
}}/>

Currently, my solution is after retrieving list data, I have to manually scroll down 1px this.listRef.scrollToOffset({offset: 1})

But it’s still a bug, I think it should only be closed after fixed by a new release.

@sahrens The list renders fine no matter what removeClippedSubviews is, as long as length is 0. Once I change back length to the screen width, regardless if removeClippedSubviews is turned off or not my list goes back to being blank until scroll.

<Flatlist style={{ flex: 1 }}/> This trick work for me without switching to ListView.

@DOHere Thank you very much for the tip.

I plan to stay with ListView as long as I can. FlatList has wasted way too much of my time.

After my experience of migrating multiple components to FlatList, I have had to roll back to ListView several times because of rendering not working properly as ListView did.

I will keep your advice in mind when I am forced to upgrade to FlatList.

I might have resolved it by only passing initialScrollIndex if the items would actually go off screen.

initialScrollIndex={sortedEntries.length > 5 ? sortedEntries.length - 1 : 0}

Feels like this kind of logic should not be necessary.

React Native FlatList shows some blank in the quickly sliding. What is the solution?

I am still having this issue with removeClippedSubviews = {false} but then setting it false also reducing my rendering performance. However, @douglasjunior solution worked perfectly fine for me without compromising performance. Thanks @douglasjunior 👍

That shouldn’t be a problem - FlatListExample has an initialNumToRender much smaller than the data size with no issue.

@sahrens I just minimized onScroll property in my comment. Remove those properties does not affect anything. After investigating more, removeClippedSubviews={false} works for me only if I add initialNumToRender={myInitialData.length} either. So my list does not render all items because my initial data size is more than the default number (10) of how many items to render in the initial batch.

Does it worth to emphasize this in FlatList docs?

@dzuncoi, @huyanhh, do you think you might be able to put together an example project?

Got something really weird.

I’m trying to scroll to the end of my horizontal pager I made out of the Flatlist with this

componentDidMount() {
    this._listRef.scrollToEnd({ animated: false });
  }

Here’s my Flatlist:

<FlatList
          ref={(flatList) => { this._listRef = flatList; }}
          data={this.props.log}
          onLayout={this.adjustPageSize}
          renderItem={this.renderPage}
          showsHorizontalScrollIndicator={false}
          getItemLayout={(data, index) => {
            const width = Dimensions.get('window').width;
            return { length: width, offset: width * index, index };
          }}
          removeClippedSubviews
          horizontal
          pagingEnabled
          directionalLockEnabled
        />

When I make it like this, my list does not render until I scroll, but when I change getItemLayout to this:

 getItemLayout={(data, index) => {
            const width = Dimensions.get('window').width;
            return { length: 0, offset: width * index, index };
          }}

with the length attribute as 0, my list renders perfectly fine, and it scrolls to the end correctly in componentDidMount

Could you explain why removeClippedSubviews fixes the issue?

I am wodering this question as well. I am using react-navigation as well. Setting removeClippedSubviews to false also fixed this for me. I’m not sure of it’s implications (reduced perf?).