diff --git a/packages/react-instantsearch-core/src/connectors/connectAutoComplete.js b/packages/react-instantsearch-core/src/connectors/connectAutoComplete.js index 41e48851cf..5257d4ec27 100644 --- a/packages/react-instantsearch-core/src/connectors/connectAutoComplete.js +++ b/packages/react-instantsearch-core/src/connectors/connectAutoComplete.js @@ -9,19 +9,18 @@ const getId = () => 'query'; function getCurrentRefinement(props, searchState, context) { const id = getId(); - return getCurrentRefinementValue( + const currentRefinement = getCurrentRefinementValue( props, searchState, context, id, - '', - currentRefinement => { - if (currentRefinement) { - return currentRefinement; - } - return ''; - } + '' ); + + if (currentRefinement) { + return currentRefinement; + } + return ''; } function getHits(searchResults) { diff --git a/packages/react-instantsearch-core/src/connectors/connectHierarchicalMenu.js b/packages/react-instantsearch-core/src/connectors/connectHierarchicalMenu.js index 6a9884f621..916a81fb0a 100644 --- a/packages/react-instantsearch-core/src/connectors/connectHierarchicalMenu.js +++ b/packages/react-instantsearch-core/src/connectors/connectHierarchicalMenu.js @@ -14,19 +14,18 @@ export const getId = props => props.attributes[0]; const namespace = 'hierarchicalMenu'; function getCurrentRefinement(props, searchState, context) { - return getCurrentRefinementValue( + const currentRefinement = getCurrentRefinementValue( props, searchState, context, `${namespace}.${getId(props)}`, - null, - currentRefinement => { - if (currentRefinement === '') { - return null; - } - return currentRefinement; - } + null ); + + if (currentRefinement === '') { + return null; + } + return currentRefinement; } function getValue(path, props, searchState, context) { diff --git a/packages/react-instantsearch-core/src/connectors/connectHitsPerPage.js b/packages/react-instantsearch-core/src/connectors/connectHitsPerPage.js index 09cc3edf63..6271ed06b1 100644 --- a/packages/react-instantsearch-core/src/connectors/connectHitsPerPage.js +++ b/packages/react-instantsearch-core/src/connectors/connectHitsPerPage.js @@ -12,19 +12,18 @@ function getId() { function getCurrentRefinement(props, searchState, context) { const id = getId(); - return getCurrentRefinementValue( + const currentRefinement = getCurrentRefinementValue( props, searchState, context, id, - null, - currentRefinement => { - if (typeof currentRefinement === 'string') { - return parseInt(currentRefinement, 10); - } - return currentRefinement; - } + null ); + + if (typeof currentRefinement === 'string') { + return parseInt(currentRefinement, 10); + } + return currentRefinement; } /** diff --git a/packages/react-instantsearch-core/src/connectors/connectInfiniteHits.js b/packages/react-instantsearch-core/src/connectors/connectInfiniteHits.js index 84f90406b4..7f688e6e18 100644 --- a/packages/react-instantsearch-core/src/connectors/connectInfiniteHits.js +++ b/packages/react-instantsearch-core/src/connectors/connectInfiniteHits.js @@ -15,19 +15,18 @@ function getId() { function getCurrentRefinement(props, searchState, context) { const id = getId(); const page = 1; - return getCurrentRefinementValue( + const currentRefinement = getCurrentRefinementValue( props, searchState, context, id, - page, - currentRefinement => { - if (typeof currentRefinement === 'string') { - currentRefinement = parseInt(currentRefinement, 10); - } - return currentRefinement; - } + page ); + + if (typeof currentRefinement === 'string') { + return parseInt(currentRefinement, 10); + } + return currentRefinement; } /** diff --git a/packages/react-instantsearch-core/src/connectors/connectMenu.js b/packages/react-instantsearch-core/src/connectors/connectMenu.js index 9cb97e3909..83fe41bce8 100644 --- a/packages/react-instantsearch-core/src/connectors/connectMenu.js +++ b/packages/react-instantsearch-core/src/connectors/connectMenu.js @@ -16,19 +16,18 @@ function getId(props) { } function getCurrentRefinement(props, searchState, context) { - return getCurrentRefinementValue( + const currentRefinement = getCurrentRefinementValue( props, searchState, context, `${namespace}.${getId(props)}`, - null, - currentRefinement => { - if (currentRefinement === '') { - return null; - } - return currentRefinement; - } + null ); + + if (currentRefinement === '') { + return null; + } + return currentRefinement; } function getValue(name, props, searchState, context) { diff --git a/packages/react-instantsearch-core/src/connectors/connectPagination.js b/packages/react-instantsearch-core/src/connectors/connectPagination.js index 5cb61aea84..45b071af7f 100644 --- a/packages/react-instantsearch-core/src/connectors/connectPagination.js +++ b/packages/react-instantsearch-core/src/connectors/connectPagination.js @@ -13,19 +13,18 @@ function getId() { function getCurrentRefinement(props, searchState, context) { const id = getId(); const page = 1; - return getCurrentRefinementValue( + const currentRefinement = getCurrentRefinementValue( props, searchState, context, id, - page, - currentRefinement => { - if (typeof currentRefinement === 'string') { - return parseInt(currentRefinement, 10); - } - return currentRefinement; - } + page ); + + if (typeof currentRefinement === 'string') { + return parseInt(currentRefinement, 10); + } + return currentRefinement; } function refine(props, searchState, nextPage, context) { diff --git a/packages/react-instantsearch-core/src/connectors/connectRange.js b/packages/react-instantsearch-core/src/connectors/connectRange.js index 16b586d5de..af68bb30e6 100644 --- a/packages/react-instantsearch-core/src/connectors/connectRange.js +++ b/packages/react-instantsearch-core/src/connectors/connectRange.js @@ -65,37 +65,31 @@ function getCurrentRange(boundaries, stats, precision) { } function getCurrentRefinement(props, searchState, currentRange, context) { - const refinement = getCurrentRefinementValue( + const { min, max } = getCurrentRefinementValue( props, searchState, context, `${namespace}.${getId(props)}`, - {}, - currentRefinement => { - const { min, max } = currentRefinement; - const isFloatPrecision = Boolean(props.precision); - - let nextMin = min; - if (typeof nextMin === 'string') { - nextMin = isFloatPrecision - ? parseFloat(nextMin) - : parseInt(nextMin, 10); - } - - let nextMax = max; - if (typeof nextMax === 'string') { - nextMax = isFloatPrecision - ? parseFloat(nextMax) - : parseInt(nextMax, 10); - } - - return { - min: nextMin, - max: nextMax, - }; - } + {} ); + const isFloatPrecision = Boolean(props.precision); + + let nextMin = min; + if (typeof nextMin === 'string') { + nextMin = isFloatPrecision ? parseFloat(nextMin) : parseInt(nextMin, 10); + } + + let nextMax = max; + if (typeof nextMax === 'string') { + nextMax = isFloatPrecision ? parseFloat(nextMax) : parseInt(nextMax, 10); + } + + const refinement = { + min: nextMin, + max: nextMax, + }; + const hasMinBound = props.min !== undefined; const hasMaxBound = props.max !== undefined; diff --git a/packages/react-instantsearch-core/src/connectors/connectRefinementList.js b/packages/react-instantsearch-core/src/connectors/connectRefinementList.js index 47adb1ad71..ab5464609a 100644 --- a/packages/react-instantsearch-core/src/connectors/connectRefinementList.js +++ b/packages/react-instantsearch-core/src/connectors/connectRefinementList.js @@ -15,25 +15,23 @@ function getId(props) { } function getCurrentRefinement(props, searchState, context) { - return getCurrentRefinementValue( + const currentRefinement = getCurrentRefinementValue( props, searchState, context, `${namespace}.${getId(props)}`, - [], - currentRefinement => { - if (typeof currentRefinement === 'string') { - // All items were unselected - if (currentRefinement === '') { - return []; - } - - // Only one item was in the searchState but we know it should be an array - return [currentRefinement]; - } - return currentRefinement; - } + [] ); + + if (typeof currentRefinement !== 'string') { + return currentRefinement; + } + + if (currentRefinement) { + return [currentRefinement]; + } + + return []; } function getValue(name, props, searchState, context) { diff --git a/packages/react-instantsearch-core/src/connectors/connectScrollTo.js b/packages/react-instantsearch-core/src/connectors/connectScrollTo.js index 1e5262ee5a..234c39ef20 100644 --- a/packages/react-instantsearch-core/src/connectors/connectScrollTo.js +++ b/packages/react-instantsearch-core/src/connectors/connectScrollTo.js @@ -35,8 +35,7 @@ export default createConnector({ searchState, this.context, id, - null, - currentRefinement => currentRefinement + null ); if (!this._prevSearchState) { diff --git a/packages/react-instantsearch-core/src/connectors/connectSearchBox.js b/packages/react-instantsearch-core/src/connectors/connectSearchBox.js index 99783adb08..542188d88c 100644 --- a/packages/react-instantsearch-core/src/connectors/connectSearchBox.js +++ b/packages/react-instantsearch-core/src/connectors/connectSearchBox.js @@ -13,19 +13,18 @@ function getId() { function getCurrentRefinement(props, searchState, context) { const id = getId(props); - return getCurrentRefinementValue( + const currentRefinement = getCurrentRefinementValue( props, searchState, context, id, - '', - currentRefinement => { - if (currentRefinement) { - return currentRefinement; - } - return ''; - } + '' ); + + if (currentRefinement) { + return currentRefinement; + } + return ''; } function refine(props, searchState, nextRefinement, context) { diff --git a/packages/react-instantsearch-core/src/connectors/connectSortBy.js b/packages/react-instantsearch-core/src/connectors/connectSortBy.js index 4614d2bdf2..65b73cffac 100644 --- a/packages/react-instantsearch-core/src/connectors/connectSortBy.js +++ b/packages/react-instantsearch-core/src/connectors/connectSortBy.js @@ -12,19 +12,18 @@ function getId() { function getCurrentRefinement(props, searchState, context) { const id = getId(props); - return getCurrentRefinementValue( + const currentRefinement = getCurrentRefinementValue( props, searchState, context, id, - null, - currentRefinement => { - if (currentRefinement) { - return currentRefinement; - } - return null; - } + null ); + + if (currentRefinement) { + return currentRefinement; + } + return null; } /** diff --git a/packages/react-instantsearch-core/src/connectors/connectToggleRefinement.js b/packages/react-instantsearch-core/src/connectors/connectToggleRefinement.js index 32bf1a773e..3f6fb497e3 100644 --- a/packages/react-instantsearch-core/src/connectors/connectToggleRefinement.js +++ b/packages/react-instantsearch-core/src/connectors/connectToggleRefinement.js @@ -16,19 +16,18 @@ function getId(props) { const namespace = 'toggle'; function getCurrentRefinement(props, searchState, context) { - return getCurrentRefinementValue( + const currentRefinement = getCurrentRefinementValue( props, searchState, context, `${namespace}.${getId(props)}`, - false, - currentRefinement => { - if (currentRefinement) { - return currentRefinement; - } - return false; - } + false ); + + if (currentRefinement) { + return currentRefinement; + } + return false; } function refine(props, searchState, nextRefinement, context) { diff --git a/packages/react-instantsearch-core/src/core/__tests__/indexUtils.js b/packages/react-instantsearch-core/src/core/__tests__/indexUtils.js index 7ebfd88de9..4a952fdecd 100644 --- a/packages/react-instantsearch-core/src/core/__tests__/indexUtils.js +++ b/packages/react-instantsearch-core/src/core/__tests__/indexUtils.js @@ -109,48 +109,42 @@ describe('utility method for manipulating the search state', () => { }, }; - expect.assertions(5); // async assertions - - let expectedRefinement = value => expect(value).toEqual('refinement'); - - getCurrentRefinementValue( + let value = getCurrentRefinementValue( {}, searchState, context, 'refinement', - null, - expectedRefinement + null ); - expectedRefinement = value => expect(value).toEqual('another'); + expect(value).toEqual('refinement'); - getCurrentRefinementValue( + value = getCurrentRefinementValue( {}, searchState, context, 'namespace.another', - null, - expectedRefinement + null ); - expectedRefinement = value => expect(value).toEqual('nested.another'); + expect(value).toEqual('another'); - getCurrentRefinementValue( + value = getCurrentRefinementValue( {}, searchState, context, 'namespace.nested.another', - null, - expectedRefinement + null ); - let value = getCurrentRefinementValue( + expect(value).toEqual('nested.another'); + + value = getCurrentRefinementValue( {}, {}, context, 'refinement', - 'defaultValue', - () => {} + 'defaultValue' ); expect(value).toEqual('defaultValue'); @@ -160,8 +154,7 @@ describe('utility method for manipulating the search state', () => { {}, context, 'refinement', - null, - () => {} + null ); expect(value).toEqual('defaultRefinement'); @@ -235,6 +228,7 @@ describe('utility method for manipulating the search state', () => { expect(results).toEqual({ hits: ['some'] }); }); }); + describe('when there are multiple index', () => { let context = { multiIndexContext: { targetedIndex: 'first' } }; it('refine with no namespace', () => { @@ -346,59 +340,52 @@ describe('utility method for manipulating the search state', () => { }, }; - expect.assertions(7); // async assertions - - let expectedRefinement = value => expect(value).toEqual('refinement'); - - getCurrentRefinementValue( + let value = getCurrentRefinementValue( {}, searchState, context, 'refinement', - null, - expectedRefinement + null ); - expectedRefinement = value => expect(value).toEqual('refinement'); + expect(value).toEqual('refinement'); - getCurrentRefinementValue( + value = getCurrentRefinementValue( {}, searchState, context, 'namespace.refinement', - null, - expectedRefinement + null ); - expectedRefinement = value => expect(value).toEqual('another'); + expect(value).toEqual('refinement'); - getCurrentRefinementValue( + value = getCurrentRefinementValue( {}, searchState, context, 'namespace.another', - null, - expectedRefinement + null ); - expectedRefinement = value => expect(value).toEqual('nested.another'); + expect(value).toEqual('another'); - getCurrentRefinementValue( + value = getCurrentRefinementValue( {}, searchState, context, 'namespace.nested.another', - null, - expectedRefinement + null ); - let value = getCurrentRefinementValue( + expect(value).toEqual('nested.another'); + + value = getCurrentRefinementValue( {}, {}, context, 'refinement', - 'defaultValue', - () => {} + 'defaultValue' ); expect(value).toEqual('defaultValue'); @@ -408,8 +395,7 @@ describe('utility method for manipulating the search state', () => { searchState, context, 'anotherNamespace.refinement.top', - 'defaultValue', - () => {} + 'defaultValue' ); expect(value).toEqual('defaultValue'); @@ -419,13 +405,32 @@ describe('utility method for manipulating the search state', () => { {}, context, 'refinement', - null, - () => {} + null ); expect(value).toEqual('defaultRefinement'); }); + it('retrieve the default refinement value there is no current refinement', () => { + const searchState = { + page: 1, + refinement: 'refinement', + indices: {}, + }; + + const defaultRefinement = null; + + const value = getCurrentRefinementValue( + {}, + searchState, + context, + 'refinement', + defaultRefinement + ); + + expect(value).toBe(defaultRefinement); + }); + it('clean up values', () => { let searchState = { page: 1, diff --git a/packages/react-instantsearch-core/src/core/indexUtils.js b/packages/react-instantsearch-core/src/core/indexUtils.js index 623d4add32..1106babf5d 100644 --- a/packages/react-instantsearch-core/src/core/indexUtils.js +++ b/packages/react-instantsearch-core/src/core/indexUtils.js @@ -1,4 +1,4 @@ -import { has, omit, get } from 'lodash'; +import { omit } from 'lodash'; export function getIndexId(context) { return context && context.multiIndexContext @@ -29,15 +29,16 @@ export function refineValue( namespace ) { if (hasMultipleIndices(context)) { + const indexId = getIndexId(context); return namespace ? refineMultiIndexWithNamespace( searchState, nextRefinement, - context, + indexId, resetPage, namespace ) - : refineMultiIndex(searchState, nextRefinement, context, resetPage); + : refineMultiIndex(searchState, nextRefinement, indexId, resetPage); } else { // When we have a multi index page with shared widgets we should also // reset their page to 1 if the resetPage is provided. Otherwise the @@ -66,27 +67,25 @@ export function refineValue( } } -function refineMultiIndex(searchState, nextRefinement, context, resetPage) { +function refineMultiIndex(searchState, nextRefinement, indexId, resetPage) { const page = resetPage ? { page: 1 } : undefined; - const indexId = getIndexId(context); - const state = has(searchState, `indices.${indexId}`) - ? { - ...searchState.indices, - [indexId]: { - ...searchState.indices[indexId], - ...nextRefinement, - ...page, - }, - } - : { - ...searchState.indices, - ...{ + const state = + searchState.indices && searchState.indices[indexId] + ? { + ...searchState.indices, [indexId]: { + ...searchState.indices[indexId], ...nextRefinement, ...page, }, - }, - }; + } + : { + ...searchState.indices, + [indexId]: { + ...nextRefinement, + ...page, + }, + }; return { ...searchState, @@ -103,35 +102,31 @@ function refineSingleIndex(searchState, nextRefinement, resetPage) { function refineMultiIndexWithNamespace( searchState, nextRefinement, - context, + indexId, resetPage, namespace ) { - const indexId = getIndexId(context); const page = resetPage ? { page: 1 } : undefined; - const state = has(searchState, `indices.${indexId}`) - ? { - ...searchState.indices, - [indexId]: { - ...searchState.indices[indexId], - ...{ + const state = + searchState.indices && searchState.indices[indexId] + ? { + ...searchState.indices, + [indexId]: { + ...searchState.indices[indexId], [namespace]: { ...searchState.indices[indexId][namespace], ...nextRefinement, }, page: 1, }, - }, - } - : { - ...searchState.indices, - ...{ + } + : { + ...searchState.indices, [indexId]: { [namespace]: nextRefinement, ...page, }, - }, - }; + }; return { ...searchState, @@ -161,44 +156,84 @@ function getNamespaceAndAttributeName(id) { return { namespace, attributeName }; } -// eslint-disable-next-line max-params +function hasRefinements({ + multiIndex, + indexId, + namespace, + attributeName, + id, + searchState, +}) { + if (multiIndex && namespace) { + return ( + searchState.indices && + searchState.indices[indexId] && + searchState.indices[indexId][namespace] && + searchState.indices[indexId][namespace].hasOwnProperty(attributeName) + ); + } + + if (multiIndex) { + return ( + searchState.indices && + searchState.indices[indexId] && + searchState.indices[indexId].hasOwnProperty(id) + ); + } + + if (namespace) { + return ( + searchState[namespace] && + searchState[namespace].hasOwnProperty(attributeName) + ); + } + + return searchState.hasOwnProperty(id); +} + +function getRefinements({ + multiIndex, + indexId, + namespace, + attributeName, + id, + searchState, +}) { + if (multiIndex && namespace) { + return searchState.indices[indexId][namespace][attributeName]; + } + if (multiIndex) { + return searchState.indices[indexId][id]; + } + if (namespace) { + return searchState[namespace][attributeName]; + } + + return searchState[id]; +} + export function getCurrentRefinementValue( props, searchState, context, id, - defaultValue, - refinementsCallback = x => x + defaultValue ) { const indexId = getIndexId(context); const { namespace, attributeName } = getNamespaceAndAttributeName(id); - const refinements = - (hasMultipleIndices(context) && - searchState.indices && - namespace && - searchState.indices[`${indexId}`] && - has(searchState.indices[`${indexId}`][namespace], `${attributeName}`)) || - (hasMultipleIndices(context) && - searchState.indices && - has(searchState, `indices.${indexId}.${id}`)) || - (!hasMultipleIndices(context) && - namespace && - has(searchState[namespace], attributeName)) || - (!hasMultipleIndices(context) && has(searchState, id)); - if (refinements) { - let currentRefinement; - - if (hasMultipleIndices(context)) { - currentRefinement = namespace - ? get(searchState.indices[`${indexId}`][namespace], attributeName) - : get(searchState.indices[indexId], id); - } else { - currentRefinement = namespace - ? get(searchState[namespace], attributeName) - : get(searchState, id); - } + const multiIndex = hasMultipleIndices(context); + const args = { + multiIndex, + indexId, + namespace, + attributeName, + id, + searchState, + }; + const hasRefinementsValue = hasRefinements(args); - return refinementsCallback(currentRefinement); + if (hasRefinementsValue) { + return getRefinements(args); } if (props.defaultRefinement) {