keyExtractor and renderItem

renderItem receives one object, not the item itself: { item, index, separators }, where separators holds highlight(), unhighlight() and updateProps() for restyling the separators around that row. Keep the function small and give the real work to a memoized row component, so an update re-renders only the rows whose props changed. Here is BookNest's row, used by every list that follows:

src/BookRow.tsx: a memoized, fixed-height catalog row (excerpt)TSX
type Props = { book: Book; selected?: boolean; onPress?: (id: number) => void };
export const ROW_HEIGHT = 96;
function BookRow({ book, selected = false, onPress }: Props) {
  return (
    <Pressable style={[s.row, selected && s.selected]} onPress={() => onPress?.(book.id)}>
      <Image source={cover(book)} style={s.cover} />
      <View style={{ flex: 1 }}>
        <Text style={s.title} numberOfLines={1}>{book.title}</Text>
        <Text style={s.meta}>{book.author} · {book.genre}</Text>
        <Text style={s.price}>${book.price.toFixed(2)}</Text>
      </View>
    </Pressable>
  );
}
export default memo(BookRow);    // re-render only when book, selected or onPress change

keyExtractor gives each row the stable identity React 7,897 needs to match old rows to new ones, exactly as key does in a web list (MERN Stack Development). Leave it out and the default in @react-native/virtualized-lists returns item.key, then item.id, then String(index), silently: a FlatList of plain title strings rendered without a single warning in Metro. Index keys work until the data changes; insert a book at the top and every key shifts, so React reuses the wrong components and per-row state moves with them. Return a string, such as String(b.id), and define renderItem and keyExtractor outside the component or in useCallback, because a new function is a new prop (extraData and Re-Rendering).