[CDX-310]: added recent searches to autocomplete dropdown#267
Conversation
This comment was marked as outdated.
This comment was marked as outdated.
|
|
||
| export type Section = AutocompleteSection | RecommendationsSection | CustomSection; | ||
| export interface RecentSearchesSection extends RecentSearchesSectionConfiguration { | ||
| data: Item[]; |
There was a problem hiding this comment.
There was a problem hiding this comment.
Narrowing down this type will require a code refactor which might be time consuming
esezen
left a comment
There was a problem hiding this comment.
Thanks for working on this!
There was a problem hiding this comment.
I think there is a bug here and also affects useRemoveSections hook. When the variant custom_autosuggest_ui_disable_recommendations_in_zero_state is active, it wipes any recentSearches section a customer has configured. A customer who only uses recentSearches (no recommendations) sees nothing under that A/B variant. The same applies to the opening the menu logic here
| * Transforms a `SearchSuggestion` into the desired URL string to be used when rendering anchor tags | ||
| * Transforms a `SearchSuggestion` or `RecentSearch` into the desired URL string | ||
| * to be used when rendering anchor tags | ||
| * i.e. <a href=getSearchResultsUrl([selected_search_suggestion])>[Search Suggestion]</a> |
There was a problem hiding this comment.
Can we update this line as well?
| isInGroupSuggestion, | ||
| isRecentSearch, | ||
| isRecommendationsSection, | ||
| isSearchSuggestion, |
|
|
||
| export type Section = AutocompleteSection | RecommendationsSection | CustomSection; | ||
| export interface RecentSearchesSection extends RecentSearchesSectionConfiguration { | ||
| data: Item[]; |
There was a problem hiding this comment.
| data: Item[]; | |
| data: RecentSearch[]; |
| export type RecentSearch = Partial<ItemBase> & | ||
| Pick<ItemBase, 'value' | 'id'> & { | ||
| section: 'recent-searches'; | ||
| ts: number; | ||
| data?: Record<string, unknown>; | ||
| }; |
There was a problem hiding this comment.
I think we can simplify and make this tighter. Wdyt?
| export type RecentSearch = Partial<ItemBase> & | |
| Pick<ItemBase, 'value' | 'id'> & { | |
| section: 'recent-searches'; | |
| ts: number; | |
| data?: Record<string, unknown>; | |
| }; | |
| export type RecentSearch = { | |
| section: 'recent-searches'; | |
| value: string; | |
| id: string; | |
| ts: number; | |
| data?: Record<string, unknown>; | |
| }; | |
There was a problem hiding this comment.
Also, I might be missing something by why do we use recentSearches in certain places and recent-searches in others?
| recentSearchesResults[displayName] = recentSearchesFromStore | ||
| .slice() | ||
| .reverse() | ||
| .slice(0, numResults || DEFAULT_NUM_RESULTS) |
There was a problem hiding this comment.
Can we document the default that we use here in docs and / or in types?
| .map(({ term, ts, data }) => ({ | ||
| ts, | ||
| data, | ||
| id: `${term}-${ts}`, |
7ea7df2 to
f4bbcdd
Compare
f4bbcdd to
8e4944c
Compare
There was a problem hiding this comment.
Code Review
This PR adds a recentSearches section type to the autocomplete dropdown zero state, reading from localStorage and integrating with tracking — the implementation is well-structured overall but has a dead-code bug and several type-safety/mutation concerns worth addressing before merge.
Inline comments: 7 discussions added
Overall Assessment:
| trackAutocompleteSelect(cioClient, selectedItem.value, selectData); | ||
|
|
||
| // Track recent searches as Search Suggestions | ||
| } else if (selectedItem.section === 'recent-searches') { |
There was a problem hiding this comment.
Critical Issue: This else if branch is dead code and will never execute. The outer if at line 41–44 already checks selectedItem?.section === 'recent-searches' and runs trackSearchSubmit + setQuery when true, so control never falls through to this branch. This means trackAutocompleteSelect is never called for recent searches, silently dropping the select-tracking event.
Fix: Move the trackAutocompleteSelect call for recent-searches inside the first if block (alongside trackSearchSubmit), or restructure so both calls execute:
if (
selectedItem?.section === 'Search Suggestions' ||
selectedItem?.section === 'recent-searches'
) {
setQuery(selectedItem.value || '');
trackSearchSubmit(cioClient, selectedItem.value, { originalQuery: previousQuery });
if (selectedItem.section === 'recent-searches') {
trackAutocompleteSelect(cioClient, selectedItem.value, {
originalQuery: previousQuery,
section: 'Search Suggestions',
});
}
}| }); | ||
|
|
||
| setRecentSearches(recentSearchesResults); | ||
| }, [recentSearchesSections]); |
There was a problem hiding this comment.
Important Issue: recentSearchesSections is an array created fresh on every render in useSectionsResults (activeSections.filter(...)), so this dependency will always be a new reference, causing the effect to re-run on every render even when the section config hasn't changed. This creates a performance issue and unnecessary state updates that can cascade into re-renders.
Fix: Memoize activeRecentSearchesSections in useSectionsResults (already done — good), but make sure the upstream activeSections array itself is stable. Alternatively, stabilize the dependency with a deep comparison (e.g., useDeepCompareMemo) or serialize the relevant fields as a primitive dependency:
}, [JSON.stringify(recentSearchesSections)]);| openOnFocus !== false && | ||
| features.featureDisplayZeroStateRecommendations | ||
| ) { | ||
| if (zeroStateActiveSections && openOnFocus !== false && activeSections.length > 0) { |
There was a problem hiding this comment.
Important Issue: The previous condition features.featureDisplayZeroStateRecommendations was a deliberate feature flag guard — removing it means the zero state now always opens on focus regardless of this feature flag. If the flag is intended to control all zero state display (not just recommendations), this behavioral change is a regression. If the intent is that recent searches + custom sections should always open (bypassing the flag), the change is correct but should be explicitly documented in a comment.
| * Stores a recent search | ||
| */ | ||
| export const storeRecentSearch = (term, suggestionData) => { | ||
| export const storeRecentSearch = (term: string, suggestionData) => { |
There was a problem hiding this comment.
Important Issue: suggestionData remains untyped (any implicitly). Given that StoreRecentSearch was just introduced, the parameter should be typed — at minimum Record<string, unknown> or a dedicated type — to keep the function consistent with the rest of the typed additions in this PR.
| const recentSearchesResults: SectionsData = {}; | ||
|
|
||
| recentSearchesSections.forEach(({ displayName, numResults }) => { | ||
| recentSearchesResults[displayName] = recentSearchesFromStore |
There was a problem hiding this comment.
Suggestion: When two recentSearches sections share the same displayName (a user misconfiguration), the second will silently overwrite the first. Consider either (a) deduplicating by displayName with a warning, or (b) keying by a composite key to prevent silent data loss.
| { term: 'blue jeans', ts: Date.now() - 2000 }, | ||
| { term: 'white sneakers', ts: Date.now() - 1000 }, | ||
| ]; | ||
| localStorage.setItem(recentSearchesStorageKey, JSON.stringify(mockRecentSearches)); |
There was a problem hiding this comment.
Suggestion: The localStorage.setItem calls at module-level (lines ~590–595, outside any story play function) will run at import time and pollute localStorage for all stories in this file, not just the recent-searches ones. This can cause cross-story test interference. Move the setup exclusively inside the play functions (which is already done redundantly there), or use a beforeEach/loaders hook instead.
No description provided.