diff --git a/packages/react-instantsearch/src/core/InstantSearch.js b/packages/react-instantsearch/src/core/InstantSearch.js index 127252284c..8997414a05 100644 --- a/packages/react-instantsearch/src/core/InstantSearch.js +++ b/packages/react-instantsearch/src/core/InstantSearch.js @@ -57,6 +57,10 @@ function validateNextProps(props, nextProps) { * @propType {string} appId - The Algolia application id. * @propType {string} apiKey - Your Algolia Search-Only API key. * @propType {string} indexName - The index in which to search. + * @propType {object} [searchParameters] - Object containing query parameters to be sent to Algolia. + * It will be overriden by the search parameters resolved via the widgets. Typical use case: + * setting the distinct setting is done by providing an object like: `{distinct: 1}`. For more information + * about the kind of object that can be provided on the [official API documentation](https://www.algolia.com/doc/rest-api/search#full-text-search-parameters). * @propType {bool=true} urlSync - Enables automatic synchronization of widgets state to the URL. See [URL Synchronization](#url-synchronization). * @propType {object} history - A custom [history](https://github.com/ReactTraining/history) to push location to. Useful for quick integration with [React Router](https://github.com/reactjs/react-router). Takes precedence over urlSync. See [Custom History](#custom-history). * @propType {number=700} threshold - Threshold in milliseconds above which new locations will be pushed to the history, instead of replacing the previous one. See [Location Debouncing](#location-debouncing). @@ -118,6 +122,7 @@ class InstantSearch extends Component { appId: props.appId, apiKey: props.apiKey, indexName: props.indexName, + searchParameters: props.searchParameters, initialState, }); @@ -208,6 +213,8 @@ InstantSearch.propTypes = { apiKey: PropTypes.string.isRequired, indexName: PropTypes.string.isRequired, + searchParameters: PropTypes.object, + history: PropTypes.object, urlSync: PropTypes.bool, threshold: PropTypes.number, diff --git a/packages/react-instantsearch/src/core/createConnector.js b/packages/react-instantsearch/src/core/createConnector.js index 68b05a4aa7..27d3cf415f 100644 --- a/packages/react-instantsearch/src/core/createConnector.js +++ b/packages/react-instantsearch/src/core/createConnector.js @@ -3,6 +3,19 @@ import {omit, has} from 'lodash'; import {shallowEqual, getDisplayName} from './utils'; +/** + * @typedef {object} ConnectorDescription + * @property {string} displayName - the displayName used by the wrapper + * @property {function} refine - a function to filter the local state + * @property {function} getSearchParameters - function transforming the local state to a SearchParameters + * @property {function} getMetadata - metadata of the widget + * @property {function} transitionState - hook after the state has changed + * @property {function} getProps - transform the state into props passed to the wrapped component. + * Receives (props, widgetStates, searchState, metadata) and returns the local state. + * @property {object} propTypes - PropTypes forwarded to the wrapped component. + * @property {object} defaultProps - default values for the props + */ + /** * Connectors are the HOC used to transform React components * into InstantSearch widgets. diff --git a/packages/react-instantsearch/src/core/createInstantSearchManager.js b/packages/react-instantsearch/src/core/createInstantSearchManager.js index 1ab9b7a1af..f2e68de07f 100644 --- a/packages/react-instantsearch/src/core/createInstantSearchManager.js +++ b/packages/react-instantsearch/src/core/createInstantSearchManager.js @@ -4,11 +4,22 @@ import algoliasearchHelper, {SearchParameters} from 'algoliasearch-helper'; import createWidgetsManager from './createWidgetsManager'; import createStore from './createStore'; +/** + * Creates a new instance of the InstantSearchManager which controls the widgets and + * trigger the search when the widgets are updated. + * @param {string} appId - the application ID + * @param {string} apiKey - the api key + * @param {string} indexName - the main index name + * @param {object} initialState - initial widget state + * @param {object} SearchParameters - optional additional parameters to send to the algolia API + * @return {InstantSearchManager} a new instance of InstantSearchManager + */ export default function createInstantSearchManager({ appId, apiKey, indexName, initialState, + searchParameters = {}, }) { const client = algoliasearch(appId, apiKey); const helper = algoliasearchHelper(client); @@ -29,23 +40,24 @@ export default function createInstantSearchManager({ .map(widget => widget.getMetadata(state)); } - function getSearchParameters(searchParameters) { + function getSearchParameters(initialSearchParameters) { return widgetsManager.getWidgets() .filter(widget => Boolean(widget.getSearchParameters)) .reduce( (res, widget) => widget.getSearchParameters(res), - searchParameters + initialSearchParameters ); } function search() { - const baseSP = new SearchParameters({index: indexName}); - // @TODO: Provide a way to configure base SearchParameters. - // We previously had a `configureSearchParameters : SP -> SP` option. - // We could also just have a `baseSearchParameters : SP` option. - const searchParameters = getSearchParameters(baseSP); + const baseSP = new SearchParameters({ + ...searchParameters, + index: indexName, + }); + const widgetSearchParameters = getSearchParameters(baseSP); - helper.searchOnce(searchParameters) + helper + .searchOnce(widgetSearchParameters) .then(({content}) => { store.setState({ ...store.getState(), diff --git a/packages/react-instantsearch/src/core/createInstantSearchManager.test.js b/packages/react-instantsearch/src/core/createInstantSearchManager.test.js index 19be17be97..891397b24f 100644 --- a/packages/react-instantsearch/src/core/createInstantSearchManager.test.js +++ b/packages/react-instantsearch/src/core/createInstantSearchManager.test.js @@ -32,7 +32,7 @@ describe('createInstantSearchManager', () => { let initialState; let ism; - function init() { + function init(otherISMParameters = {}) { createHrefForState = jest.fn(a => a); onInternalStateUpdate = jest.fn(); initialState = { @@ -46,6 +46,7 @@ describe('createInstantSearchManager', () => { initialState, createHrefForState, onInternalStateUpdate, + ...otherISMParameters, }); } @@ -133,7 +134,7 @@ describe('createInstantSearchManager', () => { }); }); - function testSearch(promise) { + function testSearch(promise, searchParameters = undefined) { const helper = { searchOnce: jest.fn(() => promise), }; @@ -153,7 +154,7 @@ describe('createInstantSearchManager', () => { }, ], })); - init(); + init({searchParameters}); return helper.searchOnce; } @@ -182,6 +183,16 @@ describe('createInstantSearchManager', () => { expect(params.query).toBe('hello'); expect(params.page).toBe(20); }); + + it('when searching it adds the searchParameters if any', () => { + const searchOnce = testSearch(new Promise(() => null), {distinct: 1}); + const onUpdate = createWidgetsManager.mock.calls[0][0]; + onUpdate(); + const params = searchOnce.mock.calls[0][0]; + expect(params.query).toBe('hello'); + expect(params.page).toBe(20); + expect(params.distinct).toBe(1); + }); }); describe('onExternalStateUpdate', () => {