Debugging React Native FlatList numColumns with Header and Footer

Author: Ho Yin Cheng

/

Created: February 15, 2018

First in a new series of posts that I’ll be calling Code Dive. I like testing the depth of my React Native knowledge by answering questions on StackOverflow. Occasionally, I’ll run into a question that asks about the same exact issue I ran into and was forced to look into the source code to understand. So I take the time to redo the code dive and write out a summary/explanation of what I discovered. For any that I feel were particularly useful, I’ll be reposting them here.

For my very first report, I’ll be explaining why headers and footers are not included in the columns for React Native’s FlatList component when using the numColumns prop. Originally asked here.

Answer

I would love to be proven wrong here, but as far as I can tell, this cannot be done using numColumns. There is a workaround which I’ll list at the end, but first the reasoning:

Explanation

I ran into this same issue but with the footer and did a code dive into the React Native source to see what was happening. First thing I did was use the inspector and I noticed that the header and footer of a FlatList are wrapped in a View while each row of columns is wrapped in CellRenderer > View. This is the first clue that FlatList does not account for headers and footers in numColumns. This is problematic because FlatList does not support masonry type layouts. This hints at the fact that there are many assumptions being made about how they are handling rendering the rows - that is, items are separated into rows to be rendered.

With these clues, I went into the source and found that FlatList passes its own custom renderItem down to VirtualizedList. Meanwhile, ListHeaderComponent and ListFooterComponent are passed down as is. They are never used in FlatList. Not a good sign as you’ll see that the aforementioned _renderItem is what handles grouping the items together into columns to render using flexDirection: 'row' and whatever is passed down with columnWrapperStyle.

But perhaps VirtualizedList is smart enough to combine the header and footer into data that gets passed into renderItem?

Sadly, this is not the case. If you follow the code in VirtualizedList, you’ll eventually see all the logic get played out in render() where:

  1. a cells array is created
  2. that first has the header pushed into it
  3. followed by all of the data pushed into it via its _pushCells helper method
    • This is where CellRenderer appears;
    • hence why headers and footers are not wrapped by this whereas the rows are.
    • This is also where FlatLists _renderItem is used explaining why you get numColumns number of items wrapped in a CellRenderer.
  4. and then finished with the footer being pushed in the same manner as the header.
  5. This cells array of React components is then eventually cloned which helps with things such as keeping scroll position.
  6. And finally rendered.

Do to this implementation, you will always get a fixed CellRenderer component which prevents headers and footers from being included as part of numColumns.

Workaround (not recommended)

If you’ve used React Native prior to FlatList being released, you’ll know that one old workaround was to be very careful with your dimensions and use flexWrap: 'wrap'. It’s actually documented within the source code as it will spit out a warning if you use this method of handling columns. Do note that it is NOT recommended to do this (I believe performance and perhaps scroll position may be some of the reasons against using this method).

If you are willing to do this though, add these styles to your contentContainerStyle:

flexDirection: 'row',
flexWrap: 'wrap',
// You'll probably also want this for handling empty cells at the end:
justifyContent: 'flex-start',

and remove the numColumns prop. Then make sure to manually size your component dimensions to get the correct looking column style layout you desire. This should allow your header and footer to render as part of the columns.


Copyright © 2011-2020 Ho Yin Cheng