orn-ui
Organism

SearchList

Requires <UIProvider> — Getting Started →

Installation

Install just this component (copies the source into your project, no npm dependency)

npx orn-ui add search-list

Or install the whole package and import it

pnpm add orn-ui
import { SearchList } from 'orn-ui/search-list';

Usage

  • when to use — A list whose main entry point is a search field. Otherwise, List.
  • searchValue / onSearchChange — Controlled: debounce and query live in your screen, not in the component.
  • loadingMode — `replace` swaps the list for a spinner; `overlay` keeps the layout, which avoids the jump while typing.
  • onScanPress — Adds the scan action next to the field — for codes, QR, barcodes.
  • hasMore / onLoadMore — Pagination with an explicit end, so the footer says when there is nothing left.
Demo clip not recorded yet — see MEDIA.md

Variants

Search + results

<SearchList
  searchValue={query}
  onSearchChange={setQuery}
  data={data.map((name) => ({ name }))}
  keyExtractor={(item) => item.name}
  renderItem={({ item }) => (
    <Card style={{ marginBottom: 8 }}>
      <Body>{item.name}</Body>
    </Card>
  )}
  emptyTitle="No matches"
/>

With scan action

<SearchList
  searchValue=""
  onSearchChange={() => {}}
  onScanPress={() => {}}
  data={[]}
  keyExtractor={(item: any) => item.name}
  renderItem={() => null}
  emptyTitle="Scan a barcode or search"
/>
Full demo source
const variants: VariantDef[] = [
  {
    label: 'Search + results',
    content: (
      <SearchList
        searchValue={query}
        onSearchChange={setQuery}
        data={data.map((name) => ({ name }))}
        keyExtractor={(item) => item.name}
        renderItem={({ item }) => (
          <Card style={{ marginBottom: 8 }}>
            <Body>{item.name}</Body>
          </Card>
        )}
        emptyTitle="No matches"
      />
    ),
  },
  {
    label: 'With scan action',
    content: (
      <SearchList
        searchValue=""
        onSearchChange={() => {}}
        onScanPress={() => {}}
        data={[]}
        keyExtractor={(item: any) => item.name}
        renderItem={() => null}
        emptyTitle="Scan a barcode or search"
      />
    ),
  },
];
return <VariantList variants={variants} fill />;

Props

NameTypeDefaultDescription
header?ReactNodeOptional chrome above the search field (e.g. back button + title).
searchValuestring
onSearchChange(text: string) => void
searchPlaceholder?string
searchIsLoading?booleanfalse
onScanPress?() => void
scanAccessibilityLabel?stringScan
extraActions?ReactNode
dataT[]
keyExtractor(item: T, index: number) => string
renderItemListRenderItem<T>
isLoading?booleanfalseInitial load: while true and there's no data, placeholders are shown.
loadingMode?'replace' | 'overlay'replace'replace' swaps the whole list for a spinner; 'overlay' shows it on top, keeping the layout.
isRefreshing?booleanfalseReload with data already on screen (pull-to-refresh).
isLoadingMore?booleanfalse
isReady?booleantrueControlled by the consumer: while false, a fullscreen spinner is shown.
loadingText?string
loadingMoreText?string
hasMore?booleanfalse
noMoreText?string
skeletonCount?number6Ghost rows for the initial load.
renderSkeletonItem?() => ReactElement<unknown, string | JSXElementConstructor<any>>Custom placeholder, to match the real shape of the item.
onLoadMore?() => void
onEndReached?() => void
onEndReachedThreshold?number0.3
onRefresh?() => void
emptyTitle?string
emptyDescription?string
emptyIconName?IconName
ListHeaderComponent?ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentType<any>
ListFooterComponent?ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentType<any>
contentContainerStyle?StyleProp<ViewStyle>
containerStyle?ViewStyle
ListComponent?ComponentType<any>FlatListList component to use (FlashList, etc).
listProps?Record<string, unknown>