extract search history into feature module

This commit is contained in:
vineyardbovines
2026-07-14 16:31:01 -04:00
parent 89ab07c4ba
commit 8bd3ca12c3
2 changed files with 104 additions and 67 deletions
+93
View File
@@ -0,0 +1,93 @@
import {useCallback} from 'react'
import {useProfilesQuery} from '#/state/queries/profile'
import {useSession} from '#/state/session'
import {
type SearchFilters,
serializeHistoryEntry,
} from '#/screens/Search/searchParams'
import {account, useStorage} from '#/storage'
import type * as bsky from '#/types/bsky'
const MAX_TERMS = 6
const MAX_PROFILES = 10
/**
* Per-account recent search history (device storage). Terms are stored as
* serialized history entries (plain string, or JSON when filters are
* attached); profiles are stored as DIDs and hydrated via useProfilesQuery,
* which keeps avatars/names fresh (stale-while-revalidate).
*/
export function useSearchHistory() {
const {currentAccount} = useSession()
const [termHistory = [], setTermHistory] = useStorage(account, [
currentAccount?.did ?? 'pwi',
'searchTermHistory',
] as const)
const [accountHistory = [], setAccountHistory] = useStorage(account, [
currentAccount?.did ?? 'pwi',
'searchAccountHistory',
])
const {data: accountHistoryProfiles} = useProfilesQuery({
handles: accountHistory,
maintainData: true,
})
const profiles =
accountHistoryProfiles?.profiles.filter(p =>
accountHistory.includes(p.did),
) ?? []
const updateSearchHistory = useCallback(
(q: string, searchFilters: SearchFilters = {}) => {
if (!q) return
/*
* Store the query plus any advanced-search filters. Term-only searches
* serialize to a plain string (back-compatible with existing history);
* filtered searches serialize to JSON. Dedupe on the serialized form.
*/
const item = serializeHistoryEntry(q, searchFilters)
const newSearchHistory = [
item,
...termHistory.filter(search => search !== item),
].slice(0, MAX_TERMS)
setTermHistory(newSearchHistory)
},
[termHistory, setTermHistory],
)
const updateProfileHistory = useCallback(
(item: bsky.profile.AnyProfileView) => {
const newAccountHistory = [
item.did,
...accountHistory.filter(p => p !== item.did),
].slice(0, MAX_PROFILES)
setAccountHistory(newAccountHistory)
},
[accountHistory, setAccountHistory],
)
const deleteSearchHistoryItem = useCallback(
(item: string) => {
setTermHistory(termHistory.filter(search => search !== item))
},
[termHistory, setTermHistory],
)
const deleteProfileHistoryItem = useCallback(
(item: bsky.profile.AnyProfileView) => {
setAccountHistory(accountHistory.filter(p => p !== item.did))
},
[accountHistory, setAccountHistory],
)
return {
termHistory,
profiles,
updateSearchHistory,
updateProfileHistory,
deleteSearchHistoryItem,
deleteProfileHistoryItem,
}
}
+11 -67
View File
@@ -28,10 +28,7 @@ import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
import {MagnifyingGlassIcon} from '#/lib/icons'
import {type NavigationProp, type SearchParams} from '#/lib/routes/types'
import {listenSoftReset} from '#/state/events'
import {
unstableCacheProfileView,
useProfilesQuery,
} from '#/state/queries/profile'
import {unstableCacheProfileView} from '#/state/queries/profile'
import {useSession} from '#/state/session'
import {
countActiveFilters,
@@ -41,7 +38,6 @@ import {
parseHistoryEntry,
readSearchFilters,
type SearchFilters,
serializeHistoryEntry,
withoutFilterParams,
} from '#/screens/Search/searchParams'
import {
@@ -62,7 +58,7 @@ import * as Toast from '#/components/Toast'
import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
import {IS_NATIVE, IS_WEB} from '#/env'
import {account, useStorage} from '#/storage'
import {useSearchHistory} from '#/features/searchHistory'
import type * as bsky from '#/types/bsky'
import {AdvancedSearchDialog} from './components/AdvancedSearchDialog'
import {AutocompleteResults} from './components/AutocompleteResults'
@@ -116,7 +112,6 @@ export function SearchScreenShell({
const route = useRoute()
const textInput = useRef<TextInput>(null)
const {t: l} = useLingui()
const {currentAccount} = useSession()
const queryClient = useQueryClient()
// Get tab parameter from route params
@@ -144,61 +139,14 @@ export function SearchScreenShell({
const [showAutocomplete, setShowAutocomplete] = useState(false)
const [termHistory = [], setTermHistory] = useStorage(account, [
currentAccount?.did ?? 'pwi',
'searchTermHistory',
] as const)
const [accountHistory = [], setAccountHistory] = useStorage(account, [
currentAccount?.did ?? 'pwi',
'searchAccountHistory',
])
const {data: accountHistoryProfiles} = useProfilesQuery({
handles: accountHistory,
maintainData: true,
})
const updateSearchHistory = useCallback(
(q: string, searchFilters: SearchFilters = {}) => {
if (!q) return
/*
* Store the query plus any advanced-search filters. Term-only searches
* serialize to a plain string (back-compatible with existing history);
* filtered searches serialize to JSON. Dedupe on the serialized form.
*/
const item = serializeHistoryEntry(q, searchFilters)
const newSearchHistory = [
item,
...termHistory.filter(search => search !== item),
].slice(0, 6)
setTermHistory(newSearchHistory)
},
[termHistory, setTermHistory],
)
const updateProfileHistory = useCallback(
(item: bsky.profile.AnyProfileView) => {
const newAccountHistory = [
item.did,
...accountHistory.filter(p => p !== item.did),
].slice(0, 10)
setAccountHistory(newAccountHistory)
},
[accountHistory, setAccountHistory],
)
const deleteSearchHistoryItem = useCallback(
(item: string) => {
setTermHistory(termHistory.filter(search => search !== item))
},
[termHistory, setTermHistory],
)
const deleteProfileHistoryItem = useCallback(
(item: bsky.profile.AnyProfileView) => {
setAccountHistory(accountHistory.filter(p => p !== item.did))
},
[accountHistory, setAccountHistory],
)
const {
termHistory,
profiles: historyProfiles,
updateSearchHistory,
updateProfileHistory,
deleteSearchHistoryItem,
deleteProfileHistoryItem,
} = useSearchHistory()
const {query, filters, setFilters, hasFilters} = useQueryManager({
initialQuery: queryParam,
@@ -683,11 +631,7 @@ export function SearchScreenShell({
) : (
<SearchHistory
searchHistory={termHistory}
selectedProfiles={
accountHistoryProfiles?.profiles.filter(p =>
accountHistory.includes(p.did),
) ?? []
}
selectedProfiles={historyProfiles}
onItemClick={handleHistoryItemClick}
onProfileClick={handleProfileClick}
onRemoveItemClick={deleteSearchHistoryItem}