Last active
November 11, 2024 13:59
-
-
Save RaphBlanchet/472ed013e05398c083caae6216b598b5 to your computer and use it in GitHub Desktop.
React-Native SectionList item layout calculations
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* ----------------------- | |
* One thing that is not really sepcified in React-Native's documentation | |
* of SectionList is what should be provided in getItemLayout function. | |
* In fact, this function is called for **every** items in your list, | |
* including the sections' header and footer EVEN IF YOU DON'T PROVIDE | |
* ANY COMPONENTS FOR THESE ITEMS. This is really important, otherwise you | |
* would have an offset in your items layout and what's displayed! | |
* This doesn't apply to "ItemSeparatorComponent", however. | |
* ----------------------- */ | |
/* ----------------------- | |
* Layout calculation is made only once, when the data changes. | |
* This helps improving performance by doing the calculations only once | |
* ----------------------- */ | |
const itemsLayout = useMemo(() => { | |
if (!data) return []; | |
const layouts: Array<ListItemLayout> = []; | |
let index = 0; | |
let offset = 0; | |
data.forEach(section => { | |
// Section header | |
const TOTAL_HEADER_HEIGHT = SECTION_HEADER_HEIGHT + ITEM_SEPARATOR_HEIGHT * 2; | |
layouts[index] = { length: TOTAL_HEADER_HEIGHT, offset, index }; | |
index++; | |
offset += TOTAL_HEADER_HEIGHT; | |
// Section items | |
section.data.forEach((_match, i) => { | |
/* ----------------------- | |
* We need to remove the last ItemSeparator from our calculations otherwise | |
* we might offset our layout a little. As per the documentation, a ItemSeparatorComponent is | |
* "Rendered in between adjacent Items within each section." | |
* ----------------------- */ | |
const MATCH_HEIGHT = | |
COMPACT_MATCH_CARD_HEIGHT + (i === section.data.length - 1 ? 0 : ITEM_SEPARATOR_HEIGHT); | |
layouts[index] = { | |
length: MATCH_HEIGHT, | |
offset, | |
index, | |
}; | |
index++; | |
offset += MATCH_HEIGHT; | |
}); | |
// Section footer | |
layouts[index] = { length: 0, offset, index }; | |
index++; | |
}); | |
return layouts; | |
}, [data]); | |
// And then my list looks something like this | |
<SectionList | |
sections={data} | |
stickySectionHeadersEnabled | |
initialScrollIndex={initialScrollIndex} | |
renderItem={({ item }) => <Match match={item} compact />} | |
getItemLayout={(d, index) => itemsLayout[index] ?? { length: 0, offset: 0, index }} | |
renderSectionHeader={({ section: { title } }) => <Header title={title} style={{ marginVertical: ITEM_SEPARATOR_HEIGHT }} />} | |
ItemSeparatorComponent={() => <View height={ITEM_SEPARATOR_HEIGHT} />} | |
/> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment