useSearchBar()
The search bar of the screen.
import { useSearchBar } from '@apexnative/navigation';
useSearchBar gets what the user does in the search field of the screen's top bar: the text as it is typed, the
Search key, the cancel, a picked suggestion. The field itself is the platform's, turned on in the screen's options;
the hook only listens. It is called in a component of the screen.
Usage
import { useState } from 'react';
import { FlatList, Text } from 'react-native';
import { defineScreen, useSearchBar } from '@apexnative/navigation';
import { useMessages } from '../data'; // your own data hook
export const InboxScreen = defineScreen({
component: Inbox,
options: (s) =>
s
.title('Inbox')
.searchBar({ visible: true, placeholder: 'Search mail' }),
});
function Inbox() {
const messages = useMessages();
const [query, setQuery] = useState('');
useSearchBar({
onChange: ({ text }) => setQuery(text),
onCancel: () => setQuery(''),
});
const shown = messages.filter((message) =>
message.subject.toLowerCase().includes(query.toLowerCase()),
);
return (
<FlatList
data={shown}
contentInsetAdjustmentBehavior="automatic"
renderItem={({ item }) => <Text>{item.subject}</Text>}
/>
);
}
As the user types, the list keeps the messages that match; Cancel brings them all back. The handlers are those of the
last render, so they need no useCallback.
Signature
function useSearchBar(handlers: {
onChange?: (event: SearchChangedEvent) => void;
onSubmit?: (event: SearchSubmittedEvent) => void;
onCancel?: (event: SearchCancelledEvent) => void;
/** (iOS 16+, Android) A suggestion of `searchBar.suggestions` is picked, by its `id`; the text stays. */
onSuggestion?: (suggestionId: string) => void;
}): void
It takes one object of handlers, each optional, and returns nothing. A handler left out is not called.
The handlers
onChange
Called on every change of the text, and when the user picks a scope or deletes a
token. It gets a SearchChangedEvent:
text: the text of the field.isFocused: whether the field has the keyboard.scope: theidof the selected scope; not set without scopes.tokens: the ids of the tokens left in the field iOS; not set without tokens.screen: the screen, as{ id, name }.
useSearchBar({
onChange: ({ text, scope }) => setFilter({ text, scope }), // your own
});
Setting the text from the app with the text option sends no onChange: the app knows the text it set.
onSubmit
Called when the user presses the Search key of the keyboard; the keyboard hides. It gets a SearchSubmittedEvent:
text, scope, tokens and screen, as onChange. The place for a search that runs on the server once the user is
done typing:
useSearchBar({
onSubmit: ({ text }) => runSearch(text), // your own request
});
onCancel
Called when the search closes, whatever closed it. It gets { screen }. Reset the filter here, so the full list shows
again.
onSuggestion
iOS 16+ and Android: called when the user picks one of the suggestions of the search bar,
with the suggestion's id. The text of the field stays as it was; the handler decides what the pick does, such as
filling the field through useScreenOptions or opening a screen.
useSearchBar({
onSuggestion: (id) => openSuggestion(id), // your own step
});
Searching from the server
onChange comes on every key. A request per key floods the server and lets an older answer overwrite a newer one.
Wait until the user pauses, and drop the answers of old queries:
import { useEffect, useState } from 'react';
// in the screen's component
const [query, setQuery] = useState('');
const [results, setResults] = useState<Mail[]>([]); // your own type
useSearchBar({ onChange: ({ text }) => setQuery(text) });
useEffect(() => {
let current = true;
const timer = setTimeout(async () => {
const found = await searchMail(query); // your own request
if (current) setResults(found);
}, 300);
return () => {
current = false;
clearTimeout(timer);
};
}, [query]);
A data library does the same with the query as its key, such as React Query's useQuery({ queryKey: ['search', query], … }). A whole search screen, with recent searches and the server, is walked through in
A search screen.
Pitfalls
- The hook doesn't show the field. Without
searchBar({ visible: true })in the options, no field shows and no handler runs. - Closing the search is its own event. Reset the screen's query in
onCancel, as in the usage above, rather than waiting for anonChangewith empty text. - A results screen hears its own events. With
resultsComponent, the component shown over the screen while it searches callsuseSearchBaritself; see A results screen. - Outside a screen the hook throws "This hook is for the screens of createNavigation".
Compared with React Navigation
React Navigation's native stack has the search field in the options, with its handlers there too:
// React Navigation
navigation.setOptions({
headerSearchBarOptions: {
onChangeText: (event) => setQuery(event.nativeEvent.text),
},
});
Here the field is declared in the options without handlers, and the component listens with this hook: the handlers see the component's state without setting the options again.
See also
- Search: where the field sits, scopes, tokens, suggestions.
- A search screen.