Skip to content

Commit

Permalink
refactor: remove recent searches from search state and invoke hook wh…
Browse files Browse the repository at this point in the history
…erever needed
  • Loading branch information
cngonzalez committed Mar 7, 2024
1 parent 8c0aa9a commit 3f8d249
Show file tree
Hide file tree
Showing 9 changed files with 71 additions and 80 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
} from '../../../../../../components'
import {useTranslation} from '../../../../../../i18n'
import {useSearchState} from '../../contexts/search/useSearchState'
import {type RecentSearch} from '../../datastores/recentSearches'
import {type RecentSearch, useRecentSearchesStore} from '../../datastores/recentSearches'
import {Instructions} from '../Instructions'
import {RecentSearchItem} from './item/RecentSearchItem'

Expand All @@ -33,9 +33,14 @@ interface RecentSearchesProps {
export function RecentSearches({inputElement}: RecentSearchesProps) {
const {
dispatch,
recentSearchesStore,
state: {filtersVisible, fullscreen, recentSearches},
state: {filtersVisible, fullscreen},
} = useSearchState()
const recentSearchesStore = useRecentSearchesStore()
const recentSearches = useMemo(
() => recentSearchesStore?.getRecentSearches(),
[recentSearchesStore],
)

const commandListRef = useRef<CommandListHandle | null>(null)

const {t} = useTranslation()
Expand All @@ -46,11 +51,10 @@ export function RecentSearches({inputElement}: RecentSearchesProps) {
*/
const handleClearRecentSearchesClick = useCallback(() => {
if (recentSearchesStore) {
const updatedRecentSearches = recentSearchesStore.removeSearch()
dispatch({recentSearches: updatedRecentSearches, type: 'RECENT_SEARCHES_SET'})
recentSearchesStore.removeSearch()
}
commandListRef?.current?.focusInputElement()
}, [dispatch, recentSearchesStore])
}, [recentSearchesStore])

const mediaIndex = useMediaIndex()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {type MouseEvent, useCallback} from 'react'
import styled from 'styled-components'

import {useSearchState} from '../../../contexts/search/useSearchState'
import {type RecentSearch} from '../../../datastores/recentSearches'
import {type RecentSearch, useRecentSearchesStore} from '../../../datastores/recentSearches'
import {DocumentTypesPill} from '../../common/DocumentTypesPill'
import {FilterPill} from '../../common/FilterPill'

Expand Down Expand Up @@ -60,7 +60,8 @@ export function RecentSearchItem({
value,
...rest
}: RecentSearchesProps) {
const {dispatch, recentSearchesStore} = useSearchState()
const {dispatch} = useSearchState()
const recentSearchesStore = useRecentSearchesStore()

// Determine how many characters are left to render type pills
const availableCharacters = maxVisibleTypePillChars - value.query.length
Expand All @@ -70,8 +71,7 @@ export function RecentSearchItem({

// Add to Local Storage
if (recentSearchesStore) {
const updatedRecentSearches = recentSearchesStore?.addSearch(value, value?.filters)
dispatch({recentSearches: updatedRecentSearches, type: 'RECENT_SEARCHES_SET'})
recentSearchesStore?.addSearch(value, value?.filters)
}
}, [dispatch, recentSearchesStore, value])

Expand All @@ -80,11 +80,10 @@ export function RecentSearchItem({
event.stopPropagation()
// Remove from Local Storage
if (recentSearchesStore) {
const updatedRecentSearches = recentSearchesStore?.removeSearchAtIndex(index)
dispatch({recentSearches: updatedRecentSearches, type: 'RECENT_SEARCHES_SET'})
recentSearchesStore?.removeSearchAtIndex(index)
}
},
[dispatch, index, recentSearchesStore],
[index, recentSearchesStore],
)

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {useTranslation} from '../../../../../../i18n'
import {type WeightedHit} from '../../../../../../search'
import {getPublishedId} from '../../../../../../util/draftUtils'
import {useSearchState} from '../../contexts/search/useSearchState'
import {useRecentSearchesStore} from '../../datastores/recentSearches'
import {NoResults} from '../NoResults'
import {SearchError} from '../SearchError'
import {SortMenu} from '../SortMenu'
Expand Down Expand Up @@ -35,11 +36,11 @@ export function SearchResults({disableIntentLink, inputElement, onItemSelect}: S
const {
dispatch,
onClose,
recentSearchesStore,
setSearchCommandList,
state: {debug, filters, fullscreen, lastActiveIndex, result, terms},
} = useSearchState()
const {t} = useTranslation()
const recentSearchesStore = useRecentSearchesStore()

const hasSearchResults = !!result.hits.length
const hasNoSearchResults = !result.hits.length && result.loaded
Expand All @@ -50,11 +51,10 @@ export function SearchResults({disableIntentLink, inputElement, onItemSelect}: S
*/
const handleSearchResultClick = useCallback(() => {
if (recentSearchesStore) {
const updatedRecentSearches = recentSearchesStore.addSearch(terms, filters)
dispatch({recentSearches: updatedRecentSearches, type: 'RECENT_SEARCHES_SET'})
recentSearchesStore.addSearch(terms, filters)
}
onClose?.()
}, [dispatch, filters, onClose, recentSearchesStore, terms])
}, [filters, onClose, recentSearchesStore, terms])

const handleEndReached = useCallback(() => {
dispatch({type: 'PAGE_INCREMENT'})
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import {createContext, type Dispatch, type SetStateAction} from 'react'

import {type CommandListHandle} from '../../../../../../components/commandList/types'
import {type RecentSearchesStore} from '../../datastores/recentSearches'
import {type SearchAction, type SearchReducerState} from './reducer'

/**
Expand All @@ -10,7 +9,6 @@ import {type SearchAction, type SearchReducerState} from './reducer'
export interface SearchContextValue {
dispatch: Dispatch<SearchAction>
onClose: (() => void) | null
recentSearchesStore?: RecentSearchesStore
searchCommandList: CommandListHandle | null
setSearchCommandList: Dispatch<SetStateAction<CommandListHandle | null>>
setOnClose: (onClose: () => void) => void
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {type SearchableType, type SearchTerms} from '../../../../../../search'
import {useCurrentUser} from '../../../../../../store'
import {useSource} from '../../../../../source'
import {SEARCH_LIMIT} from '../../constants'
import {type RecentSearch, useRecentSearchesStore} from '../../datastores/recentSearches'
import {type RecentSearch} from '../../datastores/recentSearches'
import {createFieldDefinitionDictionary, createFieldDefinitions} from '../../definitions/fields'
import {createFilterDefinitionDictionary} from '../../definitions/filters'
import {createOperatorDefinitionDictionary} from '../../definitions/operators'
Expand Down Expand Up @@ -46,25 +46,11 @@ export function SearchProvider({children, fullscreen}: SearchProviderProps) {
}
}, [filters, operators, schema])

// Create local storage store
const recentSearchesStore = useRecentSearchesStore({
fieldDefinitions,
filterDefinitions,
operatorDefinitions,
schema,
})

const recentSearches = useMemo(
() => recentSearchesStore?.getRecentSearches(),
[recentSearchesStore],
)

const initialState = useMemo(
() =>
initialSearchState({
currentUser,
fullscreen,
recentSearches,
definitions: {
fields: fieldDefinitions,
operators: operatorDefinitions,
Expand All @@ -75,14 +61,7 @@ export function SearchProvider({children, fullscreen}: SearchProviderProps) {
nextCursor: null,
},
}),
[
currentUser,
fieldDefinitions,
filterDefinitions,
fullscreen,
operatorDefinitions,
recentSearches,
],
[currentUser, fieldDefinitions, filterDefinitions, fullscreen, operatorDefinitions],
)
const [state, dispatch] = useReducer(searchReducer, initialState)

Expand Down Expand Up @@ -200,7 +179,6 @@ export function SearchProvider({children, fullscreen}: SearchProviderProps) {
value={{
dispatch,
onClose: onCloseRef?.current,
recentSearchesStore,
searchCommandList,
setSearchCommandList,
setOnClose: handleSetOnClose,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ export type SearchReducerState = PaginationState & {
lastAddedFilter?: SearchFilter | null
lastActiveIndex: number
ordering: SearchOrdering
recentSearches: RecentSearch[]
result: SearchResult
terms: RecentSearch | SearchTerms
}
Expand All @@ -60,15 +59,13 @@ export interface SearchResult {
export interface InitialSearchState {
currentUser: CurrentUser | null
fullscreen?: boolean
recentSearches?: RecentSearch[]
definitions: SearchDefinitions
pagination: PaginationState
}

export function initialSearchState({
currentUser,
fullscreen,
recentSearches = [],
definitions,
pagination,
}: InitialSearchState): SearchReducerState {
Expand All @@ -82,7 +79,6 @@ export function initialSearchState({
lastActiveIndex: -1,
ordering: ORDERINGS.relevance,
...pagination,
recentSearches,
result: {
error: null,
hasLocal: false,
Expand All @@ -101,10 +97,7 @@ export function initialSearchState({
export type FiltersVisibleSet = {type: 'FILTERS_VISIBLE_SET'; visible: boolean}
export type LastActiveIndexSet = {type: 'LAST_ACTIVE_INDEX_SET'; index: number}
export type PageIncrement = {type: 'PAGE_INCREMENT'}
export type RecentSearchesSet = {
recentSearches: RecentSearch[]
type: 'RECENT_SEARCHES_SET'
}

export type OrderingReset = {type: 'ORDERING_RESET'}
export type OrderingSet = {ordering: SearchOrdering; type: 'ORDERING_SET'}
export type SearchClear = {type: 'SEARCH_CLEAR'}
Expand Down Expand Up @@ -140,7 +133,6 @@ export type SearchAction =
| OrderingReset
| OrderingSet
| PageIncrement
| RecentSearchesSet
| SearchClear
| SearchRequestComplete
| SearchRequestError
Expand Down Expand Up @@ -198,11 +190,6 @@ export function searchReducer(state: SearchReducerState, action: SearchAction):
nextCursor: null,
terms: stripRecent(state.terms),
}
case 'RECENT_SEARCHES_SET':
return {
...state,
recentSearches: action.recentSearches,
}
case 'SEARCH_CLEAR':
return {
...state,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ const constructRecentSearchesStore = async () => {

return renderHook(
() => {
return useRecentSearchesStore(recentSearchesStoreDefinition)
return useRecentSearchesStore()
},
{wrapper: TestWrapper},
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,23 @@
import {type ObjectSchemaType, type Schema} from '@sanity/types'
import omit from 'lodash/omit'
import {useMemo} from 'react'

import {useSchema} from '../../../../../hooks'
import {type SearchTerms} from '../../../../../search'
import {type SearchFieldDefinitionDictionary} from '../definitions/fields'
import {type SearchFilterDefinitionDictionary} from '../definitions/filters'
import {type SearchOperatorDefinitionDictionary} from '../definitions/operators'
import {useSource} from '../../../../source'
import {
createFieldDefinitionDictionary,
createFieldDefinitions,
type SearchFieldDefinitionDictionary,
} from '../definitions/fields'
import {
createFilterDefinitionDictionary,
type SearchFilterDefinitionDictionary,
} from '../definitions/filters'
import {
createOperatorDefinitionDictionary,
type SearchOperatorDefinitionDictionary,
} from '../definitions/operators'
import {type SearchFilter} from '../types'
import {validateFilter} from '../utils/filterUtils'
import {getSearchableOmnisearchTypes} from '../utils/selectors'
Expand Down Expand Up @@ -49,24 +62,28 @@ interface StoredSearchItem {
terms: Omit<SearchTerms, 'types'> & {typeNames: string[]}
}

export function useRecentSearchesStore({
fieldDefinitions,
filterDefinitions,
operatorDefinitions,
schema,
}: {
fieldDefinitions: SearchFieldDefinitionDictionary
filterDefinitions: SearchFilterDefinitionDictionary
operatorDefinitions: SearchOperatorDefinitionDictionary
schema: Schema
}): RecentSearchesStore {
export function useRecentSearchesStore(): RecentSearchesStore {
const [storedSearch, setStoredSearch] = useStoredSearch()
const schema = useSchema()
const {
search: {operators, filters},
} = useSource()

// Create field, filter and operator dictionaries
const {fieldDefinitions, filterDefinitions, operatorDefinitions} = useMemo(() => {
return {
fieldDefinitions: createFieldDefinitionDictionary(createFieldDefinitions(schema, filters)),
filterDefinitions: createFilterDefinitionDictionary(filters),
operatorDefinitions: createOperatorDefinitionDictionary(operators),
}
}, [filters, operators, schema])

return {
/**
* Write a search term to Local Storage and return updated recent searches.
*/
addSearch: (searchTerm: SearchTerms, filters?: SearchFilter[]): RecentSearch[] => {
const storedFilters = (filters || []).map(
addSearch: (searchTerm: SearchTerms, searchFilters?: SearchFilter[]): RecentSearch[] => {
const storedFilters = (searchFilters || []).map(
(filter): SearchFilter => ({
fieldId: filter.fieldId,
filterName: filter.filterName,
Expand Down
20 changes: 14 additions & 6 deletions test/e2e/tests/navbar/search.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,34 +3,42 @@ import {test} from '@sanity/test'

test('searching creates saved searches', async ({page, createDraftDocument, baseURL}) => {
await createDraftDocument('/test/content/book')

await page.getByTestId('field-title').getByTestId('string-input').fill('A searchable title')
await page.getByTestId('studio-search').click()

await page.getByPlaceholder('Search', {exact: true}).fill('A search')
await page.getByPlaceholder('Search', {exact: true}).fill('A se')
await page.getByTestId('search-results').isVisible()
await page.getByTestId('search-results').click()

//search query should be saved
const localStorage = await page.evaluate(() => window.localStorage)
const keyMatch = Object.keys(localStorage).find((key) => key.startsWith('search::recent'))
const savedSearches = JSON.parse(localStorage[keyMatch!]).recentSearches
expect(savedSearches[0].terms.query).toBe('A search')
expect(savedSearches[0].terms.query).toBe('A se')

//search query should be saved after browsing
await page.goto('https://example.com')
await page.goto(baseURL ?? '/test/content')
const postNavigationLocalStorage = await page.evaluate(() => window.localStorage)

//also include going to other studio / project id?
const postNavigationSearches = JSON.parse(postNavigationLocalStorage[keyMatch!]).recentSearches
expect(postNavigationSearches[0].terms.query).toBe('A search')
expect(postNavigationSearches[0].terms.query).toBe('A se')

//search should save multiple queries
await page.getByTestId('studio-search').click()
await page.getByPlaceholder('Search', {exact: true}).fill('A search')
await page.getByTestId('search-results').isVisible()
await page.getByTestId('search-results').click()

//search queries should stack, most recent first
await page.getByTestId('studio-search').click()
await page.getByPlaceholder('Search', {exact: true}).fill('A searchable')
await page.getByTestId('search-results').isVisible()
await page.getByTestId('search-results').click()

const secondSearchStorage = await page.evaluate(() => window.localStorage)

const secondSearches = JSON.parse(secondSearchStorage[keyMatch!]).recentSearches
expect(secondSearches[0].terms.query).toBe('A searchable')
expect(secondSearches[1].terms.query).toBe('A search')
expect(secondSearches[2].terms.query).toBe('A se')
})

0 comments on commit 3f8d249

Please sign in to comment.