diff --git a/docs/search-headless.querystate.ispagination.md b/docs/search-headless.querystate.ispagination.md
new file mode 100644
index 0000000..bb32a33
--- /dev/null
+++ b/docs/search-headless.querystate.ispagination.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [@yext/search-headless](./search-headless.md) > [QueryState](./search-headless.querystate.md) > [isPagination](./search-headless.querystate.ispagination.md)
+
+## QueryState.isPagination property
+
+Whether the next query represents a pagination - in which case queryId will be maintained
+
+Signature:
+
+```typescript
+isPagination?: boolean;
+```
diff --git a/docs/search-headless.querystate.md b/docs/search-headless.querystate.md
index 78cde84..3bd35e3 100644
--- a/docs/search-headless.querystate.md
+++ b/docs/search-headless.querystate.md
@@ -17,6 +17,7 @@ export interface QueryState
| Property | Type | Description |
| --- | --- | --- |
| [input?](./search-headless.querystate.input.md) | string | (Optional) The user input used for the next search query. |
+| [isPagination?](./search-headless.querystate.ispagination.md) | boolean | (Optional) Whether the next query represents a pagination - in which case queryId will be maintained |
| [mostRecentSearch?](./search-headless.querystate.mostrecentsearch.md) | string | (Optional) The query of the most recent search. |
| [queryId?](./search-headless.querystate.queryid.md) | string | (Optional) The ID of the query from the latest search. |
| [querySource?](./search-headless.querystate.querysource.md) | [QuerySource](./search-headless.querysource.md) | (Optional) The source of the query (from a standard Search integration, a Search overlay, or from visual autocomplete). |
diff --git a/docs/search-headless.searchheadless.md b/docs/search-headless.searchheadless.md
index 4eb2e96..4440b07 100644
--- a/docs/search-headless.searchheadless.md
+++ b/docs/search-headless.searchheadless.md
@@ -41,6 +41,7 @@ export default class SearchHeadless
| [setFacetOption(fieldId, facetOption, selected)](./search-headless.searchheadless.setfacetoption.md) | | Sets a specified facet option to be selected or unselected. |
| [setFacets(facets)](./search-headless.searchheadless.setfacets.md) | | Sets [FiltersState.facets](./search-headless.filtersstate.facets.md) to the specified facets. |
| [setFilterOption(filter)](./search-headless.searchheadless.setfilteroption.md) | | Sets a static filter option and whether or not it is selected in state. |
+| [setIsPagination(input)](./search-headless.searchheadless.setispagination.md) | | Sets [QueryState.isPagination](./search-headless.querystate.ispagination.md) to the specified input. |
| [setLocationRadius(locationRadius)](./search-headless.searchheadless.setlocationradius.md) | | Sets [VerticalSearchState.locationRadius](./search-headless.verticalsearchstate.locationradius.md) to the specified number of meters. |
| [setOffset(offset)](./search-headless.searchheadless.setoffset.md) | | Sets [VerticalSearchState.offset](./search-headless.verticalsearchstate.offset.md) to the specified offset. |
| [setQuery(input)](./search-headless.searchheadless.setquery.md) | | Sets [QueryState.input](./search-headless.querystate.input.md) to the specified input. |
diff --git a/docs/search-headless.searchheadless.setispagination.md b/docs/search-headless.searchheadless.setispagination.md
new file mode 100644
index 0000000..9a071bd
--- /dev/null
+++ b/docs/search-headless.searchheadless.setispagination.md
@@ -0,0 +1,24 @@
+
+
+[Home](./index.md) > [@yext/search-headless](./search-headless.md) > [SearchHeadless](./search-headless.searchheadless.md) > [setIsPagination](./search-headless.searchheadless.setispagination.md)
+
+## SearchHeadless.setIsPagination() method
+
+Sets [QueryState.isPagination](./search-headless.querystate.ispagination.md) to the specified input.
+
+Signature:
+
+```typescript
+setIsPagination(input: boolean): void;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| input | boolean | The input to set |
+
+Returns:
+
+void
+
diff --git a/etc/search-headless.api.md b/etc/search-headless.api.md
index da54ff0..e2fe627 100644
--- a/etc/search-headless.api.md
+++ b/etc/search-headless.api.md
@@ -654,6 +654,7 @@ export enum QuerySource {
// @public
export interface QueryState {
input?: string;
+ isPagination?: boolean;
mostRecentSearch?: string;
queryId?: string;
querySource?: QuerySource;
@@ -780,6 +781,7 @@ export class SearchHeadless {
setFacetOption(fieldId: string, facetOption: FacetOption, selected: boolean): void;
setFacets(facets: DisplayableFacet[]): void;
setFilterOption(filter: SelectableStaticFilter): void;
+ setIsPagination(input: boolean): void;
setLocationRadius(locationRadius: number | undefined): void;
setOffset(offset: number): void;
setQuery(input: string): void;
diff --git a/package-lock.json b/package-lock.json
index 2da3ed3..1ec8cc0 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "@yext/search-headless",
- "version": "2.5.0",
+ "version": "2.5.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@yext/search-headless",
- "version": "2.5.0",
+ "version": "2.5.1",
"license": "BSD-3-Clause",
"dependencies": {
"@reduxjs/toolkit": "^1.8.1",
diff --git a/package.json b/package.json
index ab66c19..c571822 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@yext/search-headless",
- "version": "2.5.0",
+ "version": "2.5.1",
"description": "A library for powering UI components for Yext Search integrations",
"author": "slapshot@yext.com",
"license": "BSD-3-Clause",
diff --git a/src/models/slices/query.ts b/src/models/slices/query.ts
index d114171..760faab 100644
--- a/src/models/slices/query.ts
+++ b/src/models/slices/query.ts
@@ -30,5 +30,9 @@ export interface QueryState {
/**
* The computed intents of the mostRecentSearch, as returned by the Search API.
*/
- searchIntents?: SearchIntent[]
-}
\ No newline at end of file
+ searchIntents?: SearchIntent[],
+ /**
+ * Whether the next query represents a pagination - in which case queryId will be maintained
+ */
+ isPagination?: boolean
+}
diff --git a/src/search-headless.ts b/src/search-headless.ts
index 6d98134..3af535c 100644
--- a/src/search-headless.ts
+++ b/src/search-headless.ts
@@ -55,6 +55,15 @@ export default class SearchHeadless {
private additionalHttpHeaders?: AdditionalHttpHeaders
) {}
+ /**
+ * Sets {@link QueryState.isPagination} to the specified input.
+ *
+ * @param input - The input to set
+ */
+ setIsPagination(input: boolean): void {
+ this.stateManager.dispatchEvent('query/setIsPagination', input);
+ }
+
/**
* Sets {@link QueryState.input} to the specified input.
*
@@ -402,7 +411,7 @@ export default class SearchHeadless {
return;
}
this.stateManager.dispatchEvent('searchStatus/setIsLoading', true);
- const { input, querySource, queryTrigger } = this.state.query;
+ const { input, isPagination, queryId, querySource, queryTrigger } = this.state.query;
const skipSpellCheck = !this.state.spellCheck.enabled;
const sessionTrackingEnabled = this.state.sessionTracking.enabled;
const sessionId = this.state.sessionTracking.sessionId;
@@ -411,6 +420,7 @@ export default class SearchHeadless {
const { limit, offset, sortBys, locationRadius } = this.state.vertical;
const { referrerPageUrl, context } = this.state.meta;
const { userLocation } = this.state.location;
+ const nextQueryId = isPagination ? queryId : undefined;
const facetsToApply = facets?.map(facet => {
return {
@@ -437,6 +447,7 @@ export default class SearchHeadless {
context,
referrerPageUrl,
locationRadius,
+ queryId: nextQueryId,
additionalHttpHeaders: this.additionalHttpHeaders
};
@@ -457,6 +468,7 @@ export default class SearchHeadless {
}
this.stateManager.dispatchEvent('query/setQueryId', response.queryId);
this.stateManager.dispatchEvent('query/setMostRecentSearch', input);
+ this.stateManager.dispatchEvent('query/setIsPagination', false);
this.stateManager.dispatchEvent('filters/setFacets', response.facets);
this.stateManager.dispatchEvent('spellCheck/setResult', response.spellCheck);
this.stateManager.dispatchEvent('query/setSearchIntents', response.searchIntents || []);
diff --git a/src/slices/query.ts b/src/slices/query.ts
index 84fb2f4..cc50078 100644
--- a/src/slices/query.ts
+++ b/src/slices/query.ts
@@ -8,6 +8,9 @@ const reducers = {
setInput: (state, action: PayloadAction) => {
state.input = action.payload;
},
+ setIsPagination: (state, action: PayloadAction) => {
+ state.isPagination = action.payload;
+ },
setTrigger: (state, action: PayloadAction) => {
state.queryTrigger = action.payload;
},
diff --git a/tests/integration/verticalsearch.ts b/tests/integration/verticalsearch.ts
index bddabaa..cb3a92a 100644
--- a/tests/integration/verticalsearch.ts
+++ b/tests/integration/verticalsearch.ts
@@ -169,6 +169,23 @@ it('answers.setVerticalLimit sets the vertical limit when a number is passed to
expect(answers.state.vertical.limit).toEqual(7);
});
+it('vertical searches re-use queryId when isPagination is true', async () => {
+ const answers = createMockedHeadless( { verticalSearch: createMockSearch() }, initialState);
+ await answers.executeVerticalQuery();
+ const firstQueryId = answers.state.query.queryId;
+ answers.setIsPagination(true);
+ await answers.executeVerticalQuery();
+ expect(answers.state.query.queryId).toEqual(firstQueryId);
+});
+
+it('vertical searches change queryId when isPagination is not true', async () => {
+ const answers = createMockedHeadless( { verticalSearch: createMockSearch() }, initialState);
+ await answers.executeVerticalQuery();
+ const firstQueryId = answers.state.query.queryId;
+ await answers.executeVerticalQuery();
+ expect(answers.state.query.queryId).not.toEqual(firstQueryId);
+});
+
it('handle a rejected promise from core', async () => {
const mockSearch = createMockRejectedSearch();
const mockCore = { verticalSearch: mockSearch };
@@ -199,7 +216,11 @@ it('executeVerticalQuery passes the additional HTTP headers', async () => {
function createMockSearch() {
return jest.fn(async (_request: VerticalSearchRequest) => {
await setTimeout(0);
- return Promise.resolve({});
+ return Promise.resolve({
+ // preserves queryId if passed-in, else generates a random string
+ queryId: _request.queryId == undefined ?
+ (Math.random()).toString(36).substring(2) : _request.queryId
+ });
});
}
@@ -208,4 +229,4 @@ function createMockRejectedSearch() {
await setTimeout(0);
return Promise.reject('mock error message');
});
-}
\ No newline at end of file
+}
diff --git a/tests/unit/slices/query.ts b/tests/unit/slices/query.ts
index 97c2966..622d692 100644
--- a/tests/unit/slices/query.ts
+++ b/tests/unit/slices/query.ts
@@ -2,7 +2,7 @@ import { QuerySource, QueryTrigger } from '@yext/search-core';
import createQuerySlice from '../../../src/slices/query';
const { reducer, actions } = createQuerySlice('');
-const { setInput, setQueryId, setSource, setTrigger } = actions;
+const { setInput, setIsPagination, setQueryId, setSource, setTrigger } = actions;
describe('query slice reducer works as expected', () => {
it('setQuery action is handled properly', () => {
@@ -13,6 +13,14 @@ describe('query slice reducer works as expected', () => {
expect(actualState).toEqual(expectedState);
});
+ it('setIsPagination action is handled properly', () => {
+ const isPagination = true;
+ const expectedState = { isPagination };
+ const actualState = reducer({}, setIsPagination(isPagination));
+
+ expect(actualState).toEqual(expectedState);
+ });
+
it('setTrigger action is handled properly', () => {
const queryTrigger = QueryTrigger.Initialize;
const expectedState = { queryTrigger };
@@ -36,4 +44,4 @@ describe('query slice reducer works as expected', () => {
expect(actualState).toEqual(expectedState);
});
-});
\ No newline at end of file
+});