diff --git a/packages/react-instantsearch/src/core/InstantSearch.js b/packages/react-instantsearch/src/core/InstantSearch.js index dba56b7fe5..1c27f483fd 100644 --- a/packages/react-instantsearch/src/core/InstantSearch.js +++ b/packages/react-instantsearch/src/core/InstantSearch.js @@ -26,6 +26,7 @@ function validateNextProps(props, nextProps) { * @propType {string} appId - Your Algolia application id. * @propType {string} apiKey - Your Algolia search-only API key. * @propType {string} indexName - Main index in which to search. + * @propType {boolean} [refresh=false] - Flag to activate when the cache needs to be cleared so that the front-end is updated when a change occurs in the index. * @propType {object} [algoliaClient] - Provide a custom Algolia client instead of the internal one. * @propType {func} [onSearchStateChange] - Function to be called everytime a new search is done. Useful for [URL Routing](guide/Routing.html). * @propType {object} [searchState] - Object to inject some search state. Switches the InstantSearch component in controlled mode. Useful for [URL Routing](guide/Routing.html). @@ -70,6 +71,12 @@ class InstantSearch extends Component { this.aisManager.updateIndex(nextProps.indexName); } + if (this.props.refresh !== nextProps.refresh) { + if (nextProps.refresh) { + this.aisManager.clearCache(); + } + } + if (this.props.algoliaClient !== nextProps.algoliaClient) { this.aisManager.updateClient(nextProps.algoliaClient); } @@ -170,6 +177,8 @@ InstantSearch.propTypes = { createURL: PropTypes.func, + refresh: PropTypes.bool.isRequired, + searchState: PropTypes.object, onSearchStateChange: PropTypes.func, diff --git a/packages/react-instantsearch/src/core/InstantSearch.test.js b/packages/react-instantsearch/src/core/InstantSearch.test.js index 5d03d30ca9..5c1c8dec4d 100644 --- a/packages/react-instantsearch/src/core/InstantSearch.test.js +++ b/packages/react-instantsearch/src/core/InstantSearch.test.js @@ -9,6 +9,7 @@ Enzyme.configure({ adapter: new Adapter() }); import InstantSearch from './InstantSearch'; import createInstantSearchManager from './createInstantSearchManager'; + jest.mock('./createInstantSearchManager', () => jest.fn(() => ({ context: {}, @@ -24,6 +25,7 @@ const DEFAULT_PROPS = { root: { Root: 'div', }, + refresh: false, }; describe('InstantSearch', () => { @@ -60,6 +62,7 @@ describe('InstantSearch', () => { searchState={{}} onSearchStateChange={() => null} createURL={() => null} + refresh={false} >
@@ -139,6 +142,7 @@ describe('InstantSearch', () => { ...DEFAULT_PROPS, algoliaClient: {}, }); + expect(ism.updateClient.mock.calls).toHaveLength(1); }); @@ -255,6 +259,64 @@ describe('InstantSearch', () => { expect(ism.skipSearch.mock.calls).toHaveLength(1); }); + it('refreshes the cache when the refresh prop is set to true', () => { + const ism = { + clearCache: jest.fn(), + }; + + createInstantSearchManager.mockImplementation(() => ism); + + const wrapper = shallow( + +
+ + ); + + expect(ism.clearCache).not.toHaveBeenCalled(); + + wrapper.setProps({ + ...DEFAULT_PROPS, + refresh: false, + }); + + expect(ism.clearCache).not.toHaveBeenCalled(); + + wrapper.setProps({ + ...DEFAULT_PROPS, + refresh: true, + }); + + expect(ism.clearCache).toHaveBeenCalledTimes(1); + }); + + it('updates the index when the the index changes', () => { + const ism = { + updateIndex: jest.fn(), + }; + + createInstantSearchManager.mockImplementation(() => ism); + + const wrapper = shallow( + +
+ + ); + + expect(ism.updateIndex).not.toHaveBeenCalled(); + + wrapper.setProps({ + indexName: 'foobar', + }); + + expect(ism.updateIndex).not.toHaveBeenCalled(); + + wrapper.setProps({ + indexName: 'newindexname', + }); + + expect(ism.updateIndex).toHaveBeenCalledTimes(1); + }); + it('calls onSearchParameters with the right values if function provided', () => { const ism = { store: {}, diff --git a/packages/react-instantsearch/src/core/__snapshots__/createInstantSearch.test.js.snap b/packages/react-instantsearch/src/core/__snapshots__/createInstantSearch.test.js.snap index dee49c5446..76c1672fdc 100644 --- a/packages/react-instantsearch/src/core/__snapshots__/createInstantSearch.test.js.snap +++ b/packages/react-instantsearch/src/core/__snapshots__/createInstantSearch.test.js.snap @@ -10,6 +10,7 @@ Object { "indexName": "name", "onSearchParameters": undefined, "onSearchStateChange": undefined, + "refresh": false, "resultsState": undefined, "root": Object { "Root": "div", diff --git a/packages/react-instantsearch/src/core/createInstantSearch.js b/packages/react-instantsearch/src/core/createInstantSearch.js index c305db4ace..cc2e2dde8c 100644 --- a/packages/react-instantsearch/src/core/createInstantSearch.js +++ b/packages/react-instantsearch/src/core/createInstantSearch.js @@ -24,11 +24,16 @@ export default function createInstantSearch(defaultAlgoliaClient, root) { searchParameters: PropTypes.object, createURL: PropTypes.func, searchState: PropTypes.object, + refresh: PropTypes.bool.isRequired, onSearchStateChange: PropTypes.func, onSearchParameters: PropTypes.func, resultsState: PropTypes.oneOfType([PropTypes.object, PropTypes.array]), }; + static defaultProps = { + refresh: false, + }; + constructor(props) { super(); this.client = @@ -60,6 +65,7 @@ export default function createInstantSearch(defaultAlgoliaClient, root) { onSearchParameters={this.props.onSearchParameters} root={root} algoliaClient={this.client} + refresh={this.props.refresh} resultsState={this.props.resultsState} > {this.props.children} diff --git a/packages/react-instantsearch/src/core/createInstantSearchManager.js b/packages/react-instantsearch/src/core/createInstantSearchManager.js index 6fcc7d0482..5b8789d3b6 100644 --- a/packages/react-instantsearch/src/core/createInstantSearchManager.js +++ b/packages/react-instantsearch/src/core/createInstantSearchManager.js @@ -57,6 +57,11 @@ export default function createInstantSearchManager({ search(); } + function clearCache() { + helper.clearCache(); + search(); + } + function getMetadata(state) { return widgetsManager .getWidgets() @@ -292,6 +297,7 @@ export default function createInstantSearchManager({ onSearchForFacetValues, updateClient, updateIndex, + clearCache, skipSearch, }; } diff --git a/stories/RefreshCache.stories.js b/stories/RefreshCache.stories.js new file mode 100644 index 0000000000..9588562f4b --- /dev/null +++ b/stories/RefreshCache.stories.js @@ -0,0 +1,130 @@ +import React, { Component } from 'react'; +import { storiesOf } from '@storybook/react'; +import { InstantSearch, SearchBox } from '../packages/react-instantsearch/dom'; +import { CustomHits } from './util'; + +const stories = storiesOf('RefreshCache', module); + +class AppWithRefresh extends Component { + constructor(props) { + super(props); + this.state = { + refresh: false, + }; + } + + refresh = () => { + this.setState(prevState => ({ + refresh: !prevState.refresh, + })); + }; + + onSearchStateChange = () => { + this.setState({ refresh: false }); + }; + + render() { + const displayRefresh = `${this.state.refresh}`; + return ( + +
+

+ Feature: Refresh cache +

+

+ Adding the refresh prop to your InstantSearch component gives you + the possibility to refresh the cache. +

+ +

+ How to test it? +

+
+ By default, the 'refresh' prop is disabled. You will need + to open your network tab in the developer tools. +
    +
  1. + Type a query in the SearchBox (for instance 'clock'). + You should see 5 requests to Algolia (one per letter) +
  2. +
  3. + Type 'clock' again, you will see that no additional + query is made since the results are retrieved from the cache +
  4. +
  5. + Make sure the SearchBox is empty, click on the 'Refresh + cache' button (you should see that Refresh set to true in + the info box below the SearchBox) +
  6. +
  7. + Type your previous query again: the cache has been cleared and + you will see new requests made to Algolia +
  8. +
+
+
+
+ + + + + +
+ ); + } +} + +stories.add('with a refresh button', () => ); diff --git a/stories/util.js b/stories/util.js index f7a6334f2f..945d6f5e59 100644 --- a/stories/util.js +++ b/stories/util.js @@ -193,4 +193,4 @@ const displayName = element => { const filterProps = ['linkedStoryGroup', 'hasPlayground']; -export { displayName, filterProps, Wrap, WrapWithHits }; +export { CustomHits, displayName, filterProps, Wrap, WrapWithHits };