diff --git a/src/view/screens/Search/Search.tsx b/src/view/screens/Search/Search.tsx
index 1524c244..2335549a 100644
--- a/src/view/screens/Search/Search.tsx
+++ b/src/view/screens/Search/Search.tsx
@@ -49,11 +49,7 @@ import {ProfileCardWithFollowBtn} from '#/view/com/profile/ProfileCard'
import {List} from '#/view/com/util/List'
import {Text} from '#/view/com/util/text/Text'
import {CenteredView, ScrollView} from '#/view/com/util/Views'
-import {
- MATCH_HANDLE,
- SearchLinkCard,
- SearchProfileCard,
-} from '#/view/shell/desktop/Search'
+import {SearchLinkCard, SearchProfileCard} from '#/view/shell/desktop/Search'
import {ProfileCardFeedLoadingPlaceholder} from 'view/com/util/LoadingPlaceholder'
import {atoms as a} from '#/alf'
@@ -156,7 +152,7 @@ function useSuggestedFollows(): [
return [items, onEndReached]
}
-function SearchScreenSuggestedFollows() {
+let SearchScreenSuggestedFollows = (_props: {}): React.ReactNode => {
const pal = usePalette('default')
const [suggestions, onEndReached] = useSuggestedFollows()
@@ -180,6 +176,7 @@ function SearchScreenSuggestedFollows() {
)
}
+SearchScreenSuggestedFollows = React.memo(SearchScreenSuggestedFollows)
type SearchResultSlice =
| {
@@ -192,7 +189,7 @@ type SearchResultSlice =
key: string
}
-function SearchScreenPostResults({
+let SearchScreenPostResults = ({
query,
sort,
active,
@@ -200,7 +197,7 @@ function SearchScreenPostResults({
query: string
sort?: 'top' | 'latest'
active: boolean
-}) {
+}): React.ReactNode => {
const {_} = useLingui()
const {currentAccount} = useSession()
const [isPTR, setIsPTR] = React.useState(false)
@@ -298,14 +295,15 @@ function SearchScreenPostResults({
>
)
}
+SearchScreenPostResults = React.memo(SearchScreenPostResults)
-function SearchScreenUserResults({
+let SearchScreenUserResults = ({
query,
active,
}: {
query: string
active: boolean
-}) {
+}): React.ReactNode => {
const {_} = useLingui()
const {data: results, isFetched} = useActorSearch({
@@ -334,8 +332,9 @@ function SearchScreenUserResults({
)
}
+SearchScreenUserResults = React.memo(SearchScreenUserResults)
-export function SearchScreenInner({query}: {query?: string}) {
+let SearchScreenInner = ({query}: {query?: string}): React.ReactNode => {
const pal = usePalette('default')
const setMinimalShellMode = useSetMinimalShellMode()
const setDrawerSwipeDisabled = useSetDrawerSwipeDisabled()
@@ -467,18 +466,17 @@ export function SearchScreenInner({query}: {query?: string}) {
)
}
+SearchScreenInner = React.memo(SearchScreenInner)
export function SearchScreen(
props: NativeStackScreenProps,
) {
const navigation = useNavigation()
- const theme = useTheme()
const textInput = React.useRef(null)
const {_} = useLingui()
const pal = usePalette('default')
const {track} = useAnalytics()
const setDrawerOpen = useSetDrawerOpen()
- const moderationOpts = useModerationOpts()
const setMinimalShellMode = useSetMinimalShellMode()
const {isTabletOrDesktop, isTabletOrMobile} = useWebMediaQueries()
@@ -584,21 +582,27 @@ export function SearchScreen(
navigateToItem(searchText)
}, [navigateToItem, searchText])
- const handleHistoryItemClick = (item: string) => {
- setSearchText(item)
- navigateToItem(item)
- }
+ const onAutocompleteResultPress = React.useCallback(() => {
+ if (isWeb) {
+ setShowAutocomplete(false)
+ } else {
+ textInput.current?.blur()
+ }
+ }, [])
+
+ const handleHistoryItemClick = React.useCallback(
+ (item: string) => {
+ setSearchText(item)
+ navigateToItem(item)
+ },
+ [navigateToItem],
+ )
const onSoftReset = React.useCallback(() => {
scrollToTopWeb()
onPressCancelSearch()
}, [onPressCancelSearch])
- const queryMaybeHandle = React.useMemo(() => {
- const match = MATCH_HANDLE.exec(queryParam)
- return match && match[1]
- }, [queryParam])
-
useFocusEffect(
React.useCallback(() => {
setMinimalShellMode(false)
@@ -606,15 +610,19 @@ export function SearchScreen(
}, [onSoftReset, setMinimalShellMode]),
)
- const handleRemoveHistoryItem = (itemToRemove: string) => {
- const updatedHistory = searchHistory.filter(item => item !== itemToRemove)
- setSearchHistory(updatedHistory)
- AsyncStorage.setItem('searchHistory', JSON.stringify(updatedHistory)).catch(
- e => {
+ const handleRemoveHistoryItem = React.useCallback(
+ (itemToRemove: string) => {
+ const updatedHistory = searchHistory.filter(item => item !== itemToRemove)
+ setSearchHistory(updatedHistory)
+ AsyncStorage.setItem(
+ 'searchHistory',
+ JSON.stringify(updatedHistory),
+ ).catch(e => {
logger.error('Failed to update search history', {message: e})
- },
- )
- }
+ })
+ },
+ [searchHistory],
+ )
return (
@@ -642,81 +650,15 @@ export function SearchScreen(
/>
)}
-
- {
- textInput.current?.focus()
- }}>
-
- {
- if (isWeb) {
- // Prevent a jump on iPad by ensuring that
- // the initial focused render has no result list.
- requestAnimationFrame(() => {
- setShowAutocomplete(true)
- })
- } else {
- setShowAutocomplete(true)
- if (isIOS) {
- // We rely on selectTextOnFocus, but it's broken on iOS:
- // https://github.com/facebook/react-native/issues/41988
- textInput.current?.setSelection(0, searchText.length)
- // We still rely on selectTextOnFocus for it to be instant on Android.
- }
- }
- }}
- onChangeText={onChangeText}
- onSubmitEditing={onSubmit}
- autoFocus={false}
- accessibilityRole="search"
- accessibilityLabel={_(msg`Search`)}
- accessibilityHint=""
- autoCorrect={false}
- autoComplete="off"
- autoCapitalize="none"
- />
- {showAutocomplete && searchText.length > 0 && (
-
-
-
- )}
-
+
{showAutocomplete && (
)}
-
- {showAutocomplete && searchText.length > 0 ? (
- <>
- {(isAutocompleteFetching && !autocompleteData?.length) ||
- !moderationOpts ? (
-
- ) : (
-
-
-
- {queryMaybeHandle ? (
-
- ) : null}
-
- {autocompleteData?.map(item => (
- {
- if (isWeb) {
- setShowAutocomplete(false)
- } else {
- textInput.current?.blur()
- }
- }}
- />
- ))}
-
-
-
- )}
- >
- ) : !queryParam && showAutocomplete ? (
-
-
- {searchHistory.length > 0 && (
-
-
- Recent Searches
-
- {searchHistory.map((historyItem, index) => (
-
- handleHistoryItemClick(historyItem)}
- style={[a.flex_1, a.py_sm]}>
- {historyItem}
-
- handleRemoveHistoryItem(historyItem)}
- style={[a.px_md, a.py_xs, a.justify_center]}>
-
-
-
- ))}
-
- )}
-
-
- ) : (
+
+ {searchText.length > 0 ? (
+
+ ) : (
+
+ )}
+
+
- )}
+
)
}
+let SearchInputBox = ({
+ textInput,
+ searchText,
+ showAutocomplete,
+ setShowAutocomplete,
+ onChangeText,
+ onSubmit,
+ onPressClearQuery,
+}: {
+ textInput: React.RefObject
+ searchText: string
+ showAutocomplete: boolean
+ setShowAutocomplete: (show: boolean) => void
+ onChangeText: (text: string) => void
+ onSubmit: () => void
+ onPressClearQuery: () => void
+}): React.ReactNode => {
+ const pal = usePalette('default')
+ const {_} = useLingui()
+ const theme = useTheme()
+ return (
+ {
+ textInput.current?.focus()
+ }}>
+
+ {
+ if (isWeb) {
+ // Prevent a jump on iPad by ensuring that
+ // the initial focused render has no result list.
+ requestAnimationFrame(() => {
+ setShowAutocomplete(true)
+ })
+ } else {
+ setShowAutocomplete(true)
+ if (isIOS) {
+ // We rely on selectTextOnFocus, but it's broken on iOS:
+ // https://github.com/facebook/react-native/issues/41988
+ textInput.current?.setSelection(0, searchText.length)
+ // We still rely on selectTextOnFocus for it to be instant on Android.
+ }
+ }
+ }}
+ onChangeText={onChangeText}
+ onSubmitEditing={onSubmit}
+ autoFocus={false}
+ accessibilityRole="search"
+ accessibilityLabel={_(msg`Search`)}
+ accessibilityHint=""
+ autoCorrect={false}
+ autoComplete="off"
+ autoCapitalize="none"
+ />
+ {showAutocomplete && searchText.length > 0 && (
+
+
+
+ )}
+
+ )
+}
+SearchInputBox = React.memo(SearchInputBox)
+
+let AutocompleteResults = ({
+ isAutocompleteFetching,
+ autocompleteData,
+ searchText,
+ onSubmit,
+ onResultPress,
+}: {
+ isAutocompleteFetching: boolean
+ autocompleteData: AppBskyActorDefs.ProfileViewBasic[] | undefined
+ searchText: string
+ onSubmit: () => void
+ onResultPress: () => void
+}): React.ReactNode => {
+ const moderationOpts = useModerationOpts()
+ const {_} = useLingui()
+ return (
+ <>
+ {(isAutocompleteFetching && !autocompleteData?.length) ||
+ !moderationOpts ? (
+
+ ) : (
+
+
+ {autocompleteData?.map(item => (
+
+ ))}
+
+
+ )}
+ >
+ )
+}
+AutocompleteResults = React.memo(AutocompleteResults)
+
+function SearchHistory({
+ searchHistory,
+ onItemClick,
+ onRemoveItemClick,
+}: {
+ searchHistory: string[]
+ onItemClick: (item: string) => void
+ onRemoveItemClick: (item: string) => void
+}) {
+ const {isTabletOrDesktop} = useWebMediaQueries()
+ const pal = usePalette('default')
+ return (
+
+
+ {searchHistory.length > 0 && (
+
+
+ Recent Searches
+
+ {searchHistory.map((historyItem, index) => (
+
+ onItemClick(historyItem)}
+ hitSlop={HITSLOP_10}
+ style={[a.flex_1, a.py_sm]}>
+ {historyItem}
+
+ onRemoveItemClick(historyItem)}
+ hitSlop={HITSLOP_10}
+ style={[a.px_md, a.py_xs, a.justify_center]}>
+
+
+
+ ))}
+
+ )}
+
+
+ )
+}
+
function scrollToTopWeb() {
if (isWeb) {
window.scrollTo(0, 0)
diff --git a/src/view/shell/desktop/Search.tsx b/src/view/shell/desktop/Search.tsx
index 52f28cc6..683d4421 100644
--- a/src/view/shell/desktop/Search.tsx
+++ b/src/view/shell/desktop/Search.tsx
@@ -31,10 +31,7 @@ import {Link} from '#/view/com/util/Link'
import {UserAvatar} from '#/view/com/util/UserAvatar'
import {Text} from 'view/com/util/text/Text'
-export const MATCH_HANDLE =
- /@?([a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*(?:\.[a-zA-Z]{2,}))/
-
-export function SearchLinkCard({
+let SearchLinkCard = ({
label,
to,
onPress,
@@ -44,7 +41,7 @@ export function SearchLinkCard({
to?: string
onPress?: () => void
style?: ViewStyle
-}) {
+}): React.ReactNode => {
const pal = usePalette('default')
const inner = (
@@ -82,8 +79,10 @@ export function SearchLinkCard({
)
}
+SearchLinkCard = React.memo(SearchLinkCard)
+export {SearchLinkCard}
-export function SearchProfileCard({
+let SearchProfileCard = ({
profile,
moderation,
onPress: onPressInner,
@@ -91,7 +90,7 @@ export function SearchProfileCard({
profile: AppBskyActorDefs.ProfileViewBasic
moderation: ModerationDecision
onPress: () => void
-}) {
+}): React.ReactNode => {
const pal = usePalette('default')
const queryClient = useQueryClient()
@@ -144,6 +143,8 @@ export function SearchProfileCard({
)
}
+SearchProfileCard = React.memo(SearchProfileCard)
+export {SearchProfileCard}
export function DesktopSearch() {
const {_} = useLingui()
@@ -179,11 +180,6 @@ export function DesktopSearch() {
setIsActive(false)
}, [])
- const queryMaybeHandle = React.useMemo(() => {
- const match = MATCH_HANDLE.exec(query)
- return match && match[1]
- }, [query])
-
return (
0
+ (autocompleteData?.length ?? 0) > 0
? {borderBottomWidth: 1}
: undefined
}
/>
-
- {queryMaybeHandle ? (
-
- ) : null}
-
{autocompleteData?.map(item => (