Skip to main content

Section List

Section lists allow you to organize long lists of content by dividing them with headings.

Loading...

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.

Section List
      <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.

Section List
    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);
};