Section lists allow you to organize long lists of content by dividing them with headings.
The primary component, SectionList, acts as the main orchestrator of the entire Section List interface. It coordinates the rendering of the table of contents and individual content sections.
Section List index={index}
sectionCardsRef={sectionCardsRef}
/>
)}
data={data}
estimatedItemSize={52}
ref={tableOfContentsRef}
/>
</View>
);
};
const SectionList = () => {
const selectedItem = useSharedValue('');
const visibleIndex = useSharedValue(0);
const sectionNames = SECTIONS.map((s) => s.name);
const sectionCardsRef = useRef(null);
const tableOfContentsRef = useRef(null);
return (
<SafeAreaView style={sectionListStyles.cardsContainer}>
<TableOfContents
data={sectionNames}
visibleIndex={visibleIndex}
sectionCardsRef={sectionCardsRef}
Within SectionList, there are two key components: TableOfContents and SectionCards.
TableOfContents is responsible for rendering the list of section names as a table of contents. It receives props such as data
, visibleIndex
, sectionCardsRef
, and tableOfContentsRef
to manage navigation and synchronization between the table of contents and section content.
<Animated.Text
style={[
style,
sectionListStyles.tableOfContentsElement,
tableOfContentsElementTextStyle,
]}>
{item}
</Animated.Text>
</Pressable>
);
};
const TableOfContents = ({
data,
visibleIndex,
sectionCardsRef,
tableOfContentsRef,
}) => {
return (
<View style={sectionListStyles.tableOfContents}>
<FlashList
horizontal
showsHorizontalScrollIndicator={false}
renderItem={({ item, index }) => (
<TableOfContentsElement
item={item}
SectionCards, on the other hand, manages the rendering of individual sections and their corresponding content. It receives props: sections
, visibleIndex
, sectionCardsRef
, and tableOfContentsRef
to render the content sections and handle scrolling interactions.
marginHorizontal: 4,
margin: 8,
overflow: 'hidden',
},
tableOfContents: {
top: 0,
},
});
const SectionCards = ({
sections,
visibleIndex,
sectionCardsRef,
tableOfContentsRef,
}) => {
const { colorScheme } = useColorScheme();
const heights = sections.map((_) => SECTION_HEIGHT);
const getOffsetStarts = () =>
heights.map((v, i) => heights.slice(0, i).reduce((x, acc) => x + acc, 0));
const onScroll = (event) => {
const offset = event.nativeEvent?.contentOffset?.y;
if (offset !== undefined) {
const distancesFromTop = getOffsetStarts().map((v) =>
Math.abs(v - offset)
);
const newIndex = distancesFromTop.indexOf(
Math.min.apply(null, distancesFromTop)
);
if (visibleIndex.value !== newIndex) {
tableOfContentsRef.current?.scrollToIndex({
index: newIndex,
animated: true,
});
}
visibleIndex.value = newIndex;
}
};
const sectionNameStyle = useAnimatedStyle(() => ({
color: colorScheme === 'light' ? '#001a72' : '#f8f9ff',
}));
const renderItem = ({ item }) => {
return (
<View>
<Animated.Text style={[sectionCardStyles.header, sectionNameStyle]}>
{item.name}
</Animated.Text>
<SectionCardsElement>
<Text style={sectionCardStyles.content}>{item.content}</Text>
</SectionCardsElement>
</View>
);
};
return (
The onScroll
in SectionCards calculates the offset as the user scrolls through the content and determines which section is currently most visible on the screen. It is done by comparing the distance of each section from the top of the screen - it identifies the section closest to the viewport's top edge.
},
});
const SectionCards = ({
sections,
visibleIndex,
sectionCardsRef,
tableOfContentsRef,
}) => {
const { colorScheme } = useColorScheme();
const heights = sections.map((_) => SECTION_HEIGHT);
const getOffsetStarts = () =>
heights.map((v, i) => heights.slice(0, i).reduce((x, acc) => x + acc, 0));
const onScroll = (event) => {
const offset = event.nativeEvent?.contentOffset?.y;
if (offset !== undefined) {
const distancesFromTop = getOffsetStarts().map((v) =>
Math.abs(v - offset)
);
const newIndex = distancesFromTop.indexOf(
Math.min.apply(null, distancesFromTop)
We use the useSharedValue
hook to create mutable shared values across different components. For instance, selectedItem
and visibleIndex
are shared values used to manage the currently selected section and its visibility index.
sectionCardsRef={sectionCardsRef}
/>
Additionally, we use useAnimatedStyle
hook to define animated styles based on the shared values. Then, we apply these animated styles to components to create dynamic visual effects, such as changing font weights and adding bottom borders.
const useSelectedStyle = (selectedItem, item) =>
useAnimatedStyle(() => ({
fontWeight: selectedItem.value === item ? '600' : '400',
borderBottomWidth: selectedItem.value === item ? 1 : 0,
To enable interaction with the FlashList component - such as scrolling to specific sections, the code utilizes variables created using useRef
such as sectionCardsRef
and tableContentsRef
data={data}
estimatedItemSize={52}
Here, the debounce
function throttles the invocations of onScroll
event handler which improves the performance.
function debounce(func, timeout = 100) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => {
func(...args);
}, timeout);
};