Skip to main content

Lists

There are two ways to render lists in Kog, and choosing correctly matters more than it does on a phone, because widget memory is the scarce resource.

Small lists: .map()​

For bounded lists (menus, settings, a handful of rows), plain .map() works exactly as in React:

<View>
{items.map(item => (
<Row key={item.id} item={item} />
))}
</View>

This compiles to a minimal keyed reconciler. When items changes, new keys mount, missing keys unmount, and surviving keys move and update in place without being re-created. Each surviving row updates only the properties that changed. Use stable keys; without a key, rows are keyed by index.

Long lists: FlatList​

A thousand retained widgets will exhaust a microcontroller. FlatList renders only what's visible, using a native recycling pool: a fixed set of row widget-trees is created once, and scrolling rebinds each pooled row's data rather than creating or destroying anything.

<FlatList
data={messages}
rowHeight={56}
keyExtractor={m => m.id}
renderItem={({ item }) => (
<View style={styles.row}>
<Text style={styles.from}>{item.from}</Text>
<Text>{item.preview}</Text>
</View>
)}
/>

Because Kog components update via fine-grained effects, recycling is very cheap: rebinding a row means writing one signal, and only the text/props that differ between the old and new item touch the display. Scrolling cost is proportional to the rows entering the viewport, never to data.length.

data, renderItem, and rowHeight are required for recycling, and keyExtractor is optional (advisory under the index-based windowing). horizontal lays the list out along x, and rowHeight is then each item's width. poolSize overrides how many row widgets are recycled; by default it is the rows that fit the screen, plus two.

React Native's list chrome works too: ListHeaderComponent, ListFooterComponent, ListEmptyComponent and ItemSeparatorComponent, onEndReached with onEndReachedThreshold, and a ref with scrollToIndex, scrollToItem, scrollToOffset and scrollToEnd. Because every row slot has the same height, a header, footer or empty row takes exactly one slot, and a separator is drawn inside each item's slot. Separators receive { highlighted, leadingItem }, as in React Native; headers, footers and the empty component receive {}. numColumns, inverted and getItemLayout are not supported.

Grouped lists: SectionList​

SectionList takes React Native's sections shape and renders a header above each group. It is built on FlatList, so it recycles rows the same way, and every row and header shares the one fixed rowHeight:

import { SectionList, Text, View, StyleSheet } from 'react-native';

const SECTIONS = [
{ title: 'Fruit', data: ['Apple', 'Pear'] },
{ title: 'Veg', data: ['Leek', 'Kale', 'Okra'] },
];

export default function Groceries() {
return (
<SectionList
sections={SECTIONS}
rowHeight={32}
keyExtractor={(item) => item}
renderSectionHeader={({ section }) => <Text style={styles.header}>{section.title}</Text>}
renderItem={({ item }) => (
<View style={styles.row}>
<Text>{item}</Text>
</View>
)}
/>
);
}

const styles = StyleSheet.create({
header: { fontSize: 12, color: '#888888' },
row: { paddingLeft: 12, justifyContent: 'center' },
});

Rule of thumb: if the list scrolls and can grow beyond a couple dozen rows, use FlatList (or SectionList for groups).