Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Official Expo team skill for building native UI in Expo apps, fine-tuned for Opus models and usable with Claude Code or Cursor.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
references/search.md
1# Search23## Header Search Bar45Add a search bar to the stack header with `headerSearchBarOptions`:67```tsx8<Stack.Screen9name="index"10options={{11headerSearchBarOptions: {12placeholder: "Search",13onChangeText: (event) => console.log(event.nativeEvent.text),14},15}}16/>17```1819### Options2021```tsx22headerSearchBarOptions: {23// Placeholder text24placeholder: "Search items...",2526// Auto-capitalize behavior27autoCapitalize: "none",2829// Input type30inputType: "text", // "text" | "phone" | "number" | "email"3132// Cancel button text (iOS)33cancelButtonText: "Cancel",3435// Hide when scrolling (iOS)36hideWhenScrolling: true,3738// Hide navigation bar during search (iOS)39hideNavigationBar: true,4041// Obscure background during search (iOS)42obscureBackground: true,4344// Placement45placement: "automatic", // "automatic" | "inline" | "stacked"4647// Callbacks48onChangeText: (event) => {},49onSearchButtonPress: (event) => {},50onCancelButtonPress: (event) => {},51onFocus: () => {},52onBlur: () => {},53}54```5556## useSearch Hook5758Reusable hook for search state management:5960```tsx61import { useEffect, useState } from "react";62import { useNavigation } from "expo-router";6364export function useSearch(options: any = {}) {65const [search, setSearch] = useState("");66const navigation = useNavigation();6768useEffect(() => {69navigation.setOptions({70headerShown: true,71headerSearchBarOptions: {72...options,73onChangeText(e: any) {74setSearch(e.nativeEvent.text);75options.onChangeText?.(e);76},77onSearchButtonPress(e: any) {78setSearch(e.nativeEvent.text);79options.onSearchButtonPress?.(e);80},81onCancelButtonPress(e: any) {82setSearch("");83options.onCancelButtonPress?.(e);84},85},86});87}, [options, navigation]);8889return search;90}91```9293### Usage9495```tsx96function SearchScreen() {97const search = useSearch({ placeholder: "Search items..." });9899const filteredItems = items.filter(item =>100item.name.toLowerCase().includes(search.toLowerCase())101);102103return (104<FlatList105data={filteredItems}106renderItem={({ item }) => <ItemRow item={item} />}107/>108);109}110```111112## Filtering Patterns113114### Simple Text Filter115116```tsx117const filtered = items.filter(item =>118item.name.toLowerCase().includes(search.toLowerCase())119);120```121122### Multiple Fields123124```tsx125const filtered = items.filter(item => {126const query = search.toLowerCase();127return (128item.name.toLowerCase().includes(query) ||129item.description.toLowerCase().includes(query) ||130item.tags.some(tag => tag.toLowerCase().includes(query))131);132});133```134135### Debounced Search136137For expensive filtering or API calls:138139```tsx140import { useState, useEffect, useMemo } from "react";141142function useDebounce<T>(value: T, delay: number): T {143const [debounced, setDebounced] = useState(value);144145useEffect(() => {146const timer = setTimeout(() => setDebounced(value), delay);147return () => clearTimeout(timer);148}, [value, delay]);149150return debounced;151}152153function SearchScreen() {154const search = useSearch();155const debouncedSearch = useDebounce(search, 300);156157const filteredItems = useMemo(() =>158items.filter(item =>159item.name.toLowerCase().includes(debouncedSearch.toLowerCase())160),161[debouncedSearch]162);163164return <FlatList data={filteredItems} />;165}166```167168## Search with Native Tabs169170When using NativeTabs with a search role, the search bar integrates with the tab bar:171172```tsx173// app/_layout.tsx174<NativeTabs>175<NativeTabs.Trigger name="(home)">176<Label>Home</Label>177<Icon sf="house.fill" />178</NativeTabs.Trigger>179<NativeTabs.Trigger name="(search)" role="search">180<Label>Search</Label>181</NativeTabs.Trigger>182</NativeTabs>183```184185```tsx186// app/(search)/_layout.tsx187<Stack>188<Stack.Screen189name="index"190options={{191headerSearchBarOptions: {192placeholder: "Search...",193onChangeText: (e) => setSearch(e.nativeEvent.text),194},195}}196/>197</Stack>198```199200## Empty States201202Show appropriate UI when search returns no results:203204```tsx205function SearchResults({ search, items }) {206const filtered = items.filter(/* ... */);207208if (search && filtered.length === 0) {209return (210<View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>211<Text style={{ color: PlatformColor("secondaryLabel") }}>212No results for "{search}"213</Text>214</View>215);216}217218return <FlatList data={filtered} />;219}220```221222## Search Suggestions223224Show recent searches or suggestions:225226```tsx227function SearchScreen() {228const search = useSearch();229const [recentSearches, setRecentSearches] = useState<string[]>([]);230231if (!search && recentSearches.length > 0) {232return (233<View>234<Text style={{ color: PlatformColor("secondaryLabel") }}>235Recent Searches236</Text>237{recentSearches.map((term) => (238<Pressable key={term} onPress={() => /* apply search */}>239<Text>{term}</Text>240</Pressable>241))}242</View>243);244}245246return <SearchResults search={search} />;247}248```249