Butri
Butri

Reputation: 457

Prevent FlatList re-rendering causing performance problems

I'm building a React Native App using Hooks and ES6.

The Home screen fetches some data from API and display it as image galleries. The Home screen is the parent component which includes a slideshow gallery and a Flatlist as children.

1) Slideshow - image gallery autoplay that run some operations (fetching URL in case user clicks on them) on each image iteration (every 3 secs) - This causes the re-render on the entire parent components and its children but it renders every 3 secs as supposed

2) Flatlist - just a simple Flatlist that takes an array of images - this should only rendered once when the parent component is initially loaded first

The problem

Every 3 seconds a new image is displayed in the slideshow, a function runs to fetch some props for the image displayed, the parent component is re-rendered but there is no need for Flatlist in the second gallery to run again since the array of images is the same as initially loaded (not changed)

This is my Parent code

const getTopTenMovies = (state) => state.moviescreen.popularmovies    //fetching data from Redux
const upcomingMovies = (state) => state.moviescreen.upcomingmovies

const MoviesScreen = (props) => {
  const topTenMovies = useSelector(getTopTenMovies);
  const [imageIndex, setImageIndex] = useState("");

  /* useEffect below runs every time imageIndex changes (a new image is displayed every 3 secs) */
  useEffect(() => {  
      setCarouselMovieId(selectedImageItem.id);
      setCarouselMovieTitle(selectedImageItem.title);        
  }, [imageIndex]);

  /* user clicks on an image and is taken to another screen
  const fetchMovieHandler = async (movieId, movieTitle) => {    
      props.navigation.navigate({
        routeName: "MovieDetail",
        params: {
          assetId: movieId,
          assetName: movieTitle,
        },
      });    
  };

  return (
    <ImageGallery
      data={topTenMovies}
      currentImage=setImageIndex(index)
      ...
    />

    <View style={styles.containerRow}>
          <FlatList
            horizontal={true}
            data={upcomingMovies}
            initialNumToRender={5}
            windowSize={10}
            removeClippedSubviews={true}
            keyExtractor={(item) => item.id.toString()}
            renderItem={(itemData) => (
              <HomeItem
                id={itemData.item.id}
                poster={itemData.item.poster_path}                
                onAssetSelection={fetchMovieHandler}
              />
            )}
          />
        </View>
      </View>
  )
}

The component renders the individual images within the Flatlist and passes the parameters (movieId, movieTitle) to the fetchMovieHandler function.

HomeItem code below...

class HomeItem extends React.PureComponent {
  render() {
    const assetHandler = async () => {
      this.props.onAssetSelection(this.props.id, this.props.title);
    };
    console.log("HomeItem is called");
    return (
      <TouchableOpacity onPress={assetHandler}>
          <View>            
              <Image
                key={this.props.id}
                source={{
                  uri: `https://*******${this.props.poster}`,
                }}
              />            
          </View>   
      </TouchableOpacity>
    );
  }
}

export default React.memo(HomeItem);

Every 3 seconds the HomeItem is called and I see 10 log entries (one for each image and render - upcomingMovies has 10 images). From the UI everything looks fines since the images dont seems to change probably because I've defined the HomeItem as PureComponent and using React.memo(HomeItem) but it is causing performance issues since I'm getting the following error:

VirtualizedList: You have a large list that is slow to update - make sure your renderItem function renders components that follow React performance best practices like PureComponent, shouldComponentUpdate, etc

I tried to include the Flatlist inside the HomeItem component but the problem persists.

Upvotes: 4

Views: 9336

Answers (2)

Butri
Butri

Reputation: 457

Following further research and thanks to @landorid suggestion I figured out that I need control the renderItem of the FlatList by declaring it in a separate function with a useCallback hook and dependency on my data source.

Tried to include Flatlist in a separate PureComponent my the re-rendering kept happening.

The ideal solution is to change this code

renderItem={(itemData) => (
  <HomeItem
     id={itemData.item.id}
     poster={itemData.item.poster_path}                
     onAssetSelection={fetchMovieHandler}
  />
)}

to something like this

.....
  const renderRow = ({ itemData }) => {
    return (
      <HomeItem
        id={itemData.item.id}
        poster={itemData.item.poster_path}
        title={itemData.item.title}
        onAssetSelection={fetchMovieHandler}
      />
    );
  }; 
  keyExtractor = (item) => item.id.toString();

  return (

<FlatList
     horizontal={true}
     data={upcomingMovies}
     initialNumToRender={5}
     windowSize={10}
     removeClippedSubviews={true}
     keyExtractor={keyExtractor}
     renderItem={renderRow} 
 />

and then wrapping the renderRow() function in a useCallback hook.

The problem is that my data source data={upcomingMovies} is an array of objects with 2 levels and I need to use itemData.item.title to retrieve the title value for example.

While it works in my current renderItem settings it won't work when I use it in an external function as in

  const renderRow = ({ itemData }) => {
    return (
      <HomeItem
        id={itemData.item.id}
        poster={itemData.item.poster_path}
        title={itemData.item.title}
        onAssetSelection={fetchMovieHandler}
      />
    );
  };

I simply get, "item" variable not available

Any suggestion on how to modify the function?

Upvotes: 2

landorid
landorid

Reputation: 571

If I were you I would change the following:

  1. Don't place inline functions in FlatList (keyExtractor, renderItem)! Place it to separated function.
  2. Use simple Component extension instead of PureComponent and compare your props with `shouldComponentUpdate. (I think the passed function called onAssetSelection causes the re-render. Check it.)
  3. You don't need to use React.memo because it is for functional components.

Upvotes: 0

Related Questions