From 6f75ad6c9c0dc287b529146d7b06bade01a0098f Mon Sep 17 00:00:00 2001 From: Simon Milord Date: Thu, 19 Sep 2024 15:36:22 -0400 Subject: [PATCH] fix(quantic, searchbox): Issue with searchbox suggestions list flashing after selecting recent query (#4401) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [SFINT-5606](https://coveord.atlassian.net/browse/SFINT-5606) ### ISSUE: - After selecting a recent query, the suggestion list was "flashing". This was caused because the state of the suggestions is not cleared after selecting a recent query. So it would display first the "old" suggestions, then because we focused on the input it would THEN display the suggestions based on the input value. You can see the issue here: https://go.screenpal.com/watch/cZ1tYTV8YbT ### SOLUTION: - We are now loading the actions `loadQuerySuggestActions` in the `quanticSearchbox` in order to allow us to dispatch: `clearQuerySuggest ` after clicking on a recent query. For this we must pass the id of the suggestion as payload and therefore we needed to add `querySuggestionId ` in the state. - This allows us to clear the query suggestions state after clicking on a recent query and therefore prevent the flashing effect. Tested multiple cases where we select suggestions, recent queries, clear the recent queries, fast network, slow network, etc. Solution seems to work well without the "flashing" happening. ### DEMO: https://github.com/user-attachments/assets/c4f6a4ae-81c8-4677-af4f-b72fafded061 TESTS: Screenshot 2024-09-16 at 2 42 28 PM [SFINT-5606]: https://coveord.atlassian.net/browse/SFINT-5606?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ --- .../commerce/search-box/headless-search-box.test.ts | 1 + .../commerce/search-box/headless-search-box.ts | 1 + .../headless-standalone-search-box.test.ts | 1 + .../core/search-box/headless-core-search-box.test.ts | 1 + .../core/search-box/headless-core-search-box.ts | 6 ++++++ .../headless-standalone-search-box.test.ts | 1 + .../query-suggest/query-suggest-actions-loader.ts | 4 ++-- .../features/query-suggest/query-suggest-actions.ts | 8 ++++---- packages/headless/src/insight.index.ts | 1 + .../default/lwc/quanticSearchBox/quanticSearchBox.js | 12 +++++++++++- .../quanticStandaloneSearchBox.js | 1 + 11 files changed, 30 insertions(+), 7 deletions(-) diff --git a/packages/headless/src/controllers/commerce/search-box/headless-search-box.test.ts b/packages/headless/src/controllers/commerce/search-box/headless-search-box.test.ts index dd72cd61eed..19c7b46e1f9 100644 --- a/packages/headless/src/controllers/commerce/search-box/headless-search-box.test.ts +++ b/packages/headless/src/controllers/commerce/search-box/headless-search-box.test.ts @@ -138,6 +138,7 @@ describe('headless search box', () => { describe('#state', () => { it('is as expected', () => { expect(searchBox.state).toEqual({ + searchBoxId: id, value: state.querySet[id], suggestions: state.querySuggest[id]!.completions.map((completion) => ({ highlightedValue: 'hilighted', diff --git a/packages/headless/src/controllers/commerce/search-box/headless-search-box.ts b/packages/headless/src/controllers/commerce/search-box/headless-search-box.ts index 77b9356b362..55135f7c5b0 100644 --- a/packages/headless/src/controllers/commerce/search-box/headless-search-box.ts +++ b/packages/headless/src/controllers/commerce/search-box/headless-search-box.ts @@ -160,6 +160,7 @@ export function buildSearchBox( : false; return { + searchBoxId: id, value: getValue(), suggestions, isLoading: getState().commerceSearch.isLoading, diff --git a/packages/headless/src/controllers/commerce/standalone-search-box/headless-standalone-search-box.test.ts b/packages/headless/src/controllers/commerce/standalone-search-box/headless-standalone-search-box.test.ts index 5f73b128e94..5eada64f503 100644 --- a/packages/headless/src/controllers/commerce/standalone-search-box/headless-standalone-search-box.test.ts +++ b/packages/headless/src/controllers/commerce/standalone-search-box/headless-standalone-search-box.test.ts @@ -102,6 +102,7 @@ describe('headless standalone searchBox', () => { it('should return the right state', () => { expect(searchBox.state).toEqual({ + searchBoxId: id, value: state.querySet[id], suggestions: state.querySuggest[id]!.completions.map((completion) => ({ value: completion.expression, diff --git a/packages/headless/src/controllers/core/search-box/headless-core-search-box.test.ts b/packages/headless/src/controllers/core/search-box/headless-core-search-box.test.ts index 906ef82777b..0e183173d09 100644 --- a/packages/headless/src/controllers/core/search-box/headless-core-search-box.test.ts +++ b/packages/headless/src/controllers/core/search-box/headless-core-search-box.test.ts @@ -143,6 +143,7 @@ describe('headless CoreSearchBox', () => { it('should return the right state', () => { expect(searchBox.state).toEqual({ + searchBoxId: id, value: state.querySet[id], suggestions: state.querySuggest[id]!.completions.map((completion) => ({ highlightedValue: 'hilighted', diff --git a/packages/headless/src/controllers/core/search-box/headless-core-search-box.ts b/packages/headless/src/controllers/core/search-box/headless-core-search-box.ts index fd4e7f07628..338d87330fc 100644 --- a/packages/headless/src/controllers/core/search-box/headless-core-search-box.ts +++ b/packages/headless/src/controllers/core/search-box/headless-core-search-box.ts @@ -137,6 +137,11 @@ export interface SearchBoxState { * Determines if a query suggest request is in progress. */ isLoadingSuggestions: boolean; + + /** + * The search box ID. + */ + searchBoxId: string; } export interface Suggestion { @@ -306,6 +311,7 @@ export function buildCoreSearchBox( : false; return { + searchBoxId: id, value: getValue(), suggestions, isLoading: state.search.isLoading, diff --git a/packages/headless/src/controllers/standalone-search-box/headless-standalone-search-box.test.ts b/packages/headless/src/controllers/standalone-search-box/headless-standalone-search-box.test.ts index e436dace5d1..27678fa3f11 100644 --- a/packages/headless/src/controllers/standalone-search-box/headless-standalone-search-box.test.ts +++ b/packages/headless/src/controllers/standalone-search-box/headless-standalone-search-box.test.ts @@ -110,6 +110,7 @@ describe('headless standalone searchBox', () => { it('should return the right state', () => { expect(searchBox.state).toEqual({ + searchBoxId: id, value: state.querySet[id], suggestions: state.querySuggest[id]!.completions.map((completion) => ({ value: completion.expression, diff --git a/packages/headless/src/features/query-suggest/query-suggest-actions-loader.ts b/packages/headless/src/features/query-suggest/query-suggest-actions-loader.ts index 43f39d04c64..0f6e5f541cb 100644 --- a/packages/headless/src/features/query-suggest/query-suggest-actions-loader.ts +++ b/packages/headless/src/features/query-suggest/query-suggest-actions-loader.ts @@ -1,6 +1,6 @@ import {AsyncThunkAction, PayloadAction} from '@reduxjs/toolkit'; import {AsyncThunkSearchOptions} from '../../api/search/search-api-client'; -import {SearchEngine} from '../../app/search-engine/search-engine'; +import {CoreEngine} from '../../app/engine'; import {querySetReducer as querySet} from '../../features/query-set/query-set-slice'; import {querySuggestReducer as querySuggest} from '../../features/query-suggest/query-suggest-slice'; import { @@ -79,7 +79,7 @@ export interface QuerySuggestActionCreators { * @returns An object holding the action creators. */ export function loadQuerySuggestActions( - engine: SearchEngine + engine: CoreEngine ): QuerySuggestActionCreators { engine.addReducers({querySuggest, querySet}); diff --git a/packages/headless/src/features/query-suggest/query-suggest-actions.ts b/packages/headless/src/features/query-suggest/query-suggest-actions.ts index 52f61ce7b33..1a87365d49a 100644 --- a/packages/headless/src/features/query-suggest/query-suggest-actions.ts +++ b/packages/headless/src/features/query-suggest/query-suggest-actions.ts @@ -43,7 +43,7 @@ export interface QuerySuggestionID { export interface RegisterQuerySuggestActionCreatorPayload { /** - * A unique identifier for the new query suggest entity (e.g., `b953ab2e-022b-4de4-903f-68b2c0682942`). + * A unique identifier for the new query suggest entity (e.g., `b953ab2e-022b-4de4-903f-68b2c0682942`). Usually, this will be the ID of the search box controller that requests the query suggestions. */ id: string; @@ -71,7 +71,7 @@ export const unregisterQuerySuggest = createAction( export interface SelectQuerySuggestionActionCreatorPayload { /** - * The unique identifier of the target query suggest entity (e.g., `b953ab2e-022b-4de4-903f-68b2c0682942`). + * The unique identifier of the target query suggest entity (e.g., `b953ab2e-022b-4de4-903f-68b2c0682942`). Usually, this will be the ID of the search box controller that requests the query suggestions. */ id: string; @@ -92,7 +92,7 @@ export const selectQuerySuggestion = createAction( export interface ClearQuerySuggestActionCreatorPayload { /** - * The unique identifier of the target query suggest entity (e.g., `b953ab2e-022b-4de4-903f-68b2c0682942`). + * The unique identifier of the target query suggest entity (e.g., `b953ab2e-022b-4de4-903f-68b2c0682942`). Usually, this will be the ID of the search box controller that requests the query suggestions. */ id: string; } @@ -105,7 +105,7 @@ export const clearQuerySuggest = createAction( export interface FetchQuerySuggestionsActionCreatorPayload { /** - * The unique identifier of the target query suggest entity (e.g., `b953ab2e-022b-4de4-903f-68b2c0682942`). + * The unique identifier of the target query suggest entity (e.g., `b953ab2e-022b-4de4-903f-68b2c0682942`). Usually, this will be the ID of the search box controller that requests the query suggestions. */ id: string; } diff --git a/packages/headless/src/insight.index.ts b/packages/headless/src/insight.index.ts index 5df79931354..56534085fec 100644 --- a/packages/headless/src/insight.index.ts +++ b/packages/headless/src/insight.index.ts @@ -45,6 +45,7 @@ export * from './features/analytics/generic-analytics-actions-loader'; export * from './features/question-answering/question-answering-actions-loader'; export * from './features/folding/folding-actions-loader'; export * from './features/insight-user-actions/insight-user-actions-loader'; +export * from './features/query-suggest/query-suggest-actions-loader'; // Controllers export type { diff --git a/packages/quantic/force-app/main/default/lwc/quanticSearchBox/quanticSearchBox.js b/packages/quantic/force-app/main/default/lwc/quanticSearchBox/quanticSearchBox.js index 5f4400d0ab6..6287c7970c8 100644 --- a/packages/quantic/force-app/main/default/lwc/quanticSearchBox/quanticSearchBox.js +++ b/packages/quantic/force-app/main/default/lwc/quanticSearchBox/quanticSearchBox.js @@ -89,6 +89,7 @@ export default class QuanticSearchBox extends LightningElement { * @param {SearchEngine} engine */ initialize = (engine) => { + this.engine = engine; this.headless = getHeadlessBundle(this.engineId); this.searchBox = this.headless.buildSearchBox(engine, { options: { @@ -102,6 +103,10 @@ export default class QuanticSearchBox extends LightningElement { }, }); + this.actions = { + ...this.headless.loadQuerySuggestActions(engine), + }; + if (!this.disableRecentQueries && this.headless.buildRecentQueriesList) { this.localStorageKey = `${this.engineId}_quantic-recent-queries`; this.recentQueriesList = this.headless.buildRecentQueriesList(engine, { @@ -202,7 +207,7 @@ export default class QuanticSearchBox extends LightningElement { }; /** - * Handles the selection of a suggestion. + * Handles the selection of a suggestion or a recent query. */ selectSuggestion = (event) => { event.stopPropagation(); @@ -214,6 +219,11 @@ export default class QuanticSearchBox extends LightningElement { this.recentQueriesList.executeRecentQuery( this.recentQueries.indexOf(value) ); + this.engine.dispatch( + this.actions.clearQuerySuggest({ + id: this.state.searchBoxId, + }) + ); } else { this.searchBox?.selectSuggestion(value); } diff --git a/packages/quantic/force-app/main/default/lwc/quanticStandaloneSearchBox/quanticStandaloneSearchBox.js b/packages/quantic/force-app/main/default/lwc/quanticStandaloneSearchBox/quanticStandaloneSearchBox.js index e4d5880ed76..8d90e3748ba 100644 --- a/packages/quantic/force-app/main/default/lwc/quanticStandaloneSearchBox/quanticStandaloneSearchBox.js +++ b/packages/quantic/force-app/main/default/lwc/quanticStandaloneSearchBox/quanticStandaloneSearchBox.js @@ -98,6 +98,7 @@ export default class QuanticStandaloneSearchBox extends NavigationMixin( cause: '', metadata: null, }, + searchBoxId: '', redirectTo: null, suggestions: [], value: '',