Catalog Screen

Building BookNest's Catalog Screen

The catalog screen replaces the ScrollView of Scaffolding BookNest. A search box filters titles and authors, a toggle switches between a flat list and genre sections, and pull to refresh reloads the data. Both list types accept the same props, so they share one object:

src/CatalogScreen.tsx: search, grouping and refresh over one set of props (excerpt)TSX
  const shown = useMemo(() => books.filter((b) =>
    `${b.title} ${b.author}`.toLowerCase().includes(query.trim().toLowerCase())), [books, query]);
// refresh() reloads page 0 with fetchPage and stamps `updated`
  const shared = {                       // props both list types accept
    keyExtractor: (b: Book) => String(b.id), renderItem, refreshing, onRefresh: refresh,
    ItemSeparatorComponent: () => <View style={s.separator} />,
    keyboardShouldPersistTaps: 'handled' as const,
    ListEmptyComponent: <Text style={s.note}>No books match "{query}".</Text>,
    ListFooterComponent: shown.length ? <Text style={s.note}>{shown.length} of {books.length}
      {' '}books · updated {updated.toTimeString().slice(0, 5)}</Text> : null,
  };
  return (
    <View style={{ flex: 1 }}>
      <TextInput placeholder="Search title or author" value={query} onChangeText={setQuery}
        style={{ margin: 12, padding: 12, borderWidth: 1, borderRadius: 8 }} />
      <Pressable onPress={() => setGrouped(!grouped)}>
        <Text style={s.header}>{grouped ? 'By genre ▾' : 'All books ▾'}</Text>
      </Pressable>
      {grouped
        ? <SectionList {...shared} sections={byGenre(shown)} stickySectionHeadersEnabled
            renderSectionHeader={({ section }) => <Header title={section.title} />} />
        : <FlatList {...shared} data={shown} />}
    </View>
Genres with a sticky header (Section 3.8.9), the catalog screen, and the same screen after searching for "the" and switching to genres
Genres with a sticky header (Sticky Section Headers), the catalog screen, and the same screen after searching for "the" and switching to genres

useMemo refilters only when the books or the query change, keyboardShouldPersistTaps="handled" lets a row respond while the keyboard is open, and the empty component answers a search with no hits. Typing "the" left three books, and the toggle regrouped them under Fiction, Science Fiction and Technology. Query and Zustand moves the books to TanStack Query 79,069 and Navigation opens a book's detail screen from BookRow's onPress.