Skip to content
This repository has been archived by the owner on Dec 30, 2022. It is now read-only.

feat(refreshcache): add prop refresh to InstantSearch instance #619

Merged
merged 4 commits into from
Nov 21, 2017
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions packages/react-instantsearch/src/core/InstantSearch.js
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -170,6 +177,8 @@ InstantSearch.propTypes = {

createURL: PropTypes.func,

refresh: PropTypes.bool.isRequired,

searchState: PropTypes.object,
onSearchStateChange: PropTypes.func,

Expand Down
62 changes: 62 additions & 0 deletions packages/react-instantsearch/src/core/InstantSearch.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Enzyme.configure({ adapter: new Adapter() });
import InstantSearch from './InstantSearch';

import createInstantSearchManager from './createInstantSearchManager';

jest.mock('./createInstantSearchManager', () =>
jest.fn(() => ({
context: {},
Expand All @@ -24,6 +25,7 @@ const DEFAULT_PROPS = {
root: {
Root: 'div',
},
refresh: false,
};

describe('InstantSearch', () => {
Expand Down Expand Up @@ -60,6 +62,7 @@ describe('InstantSearch', () => {
searchState={{}}
onSearchStateChange={() => null}
createURL={() => null}
refresh={false}
>
<div />
</InstantSearch>
Expand Down Expand Up @@ -139,6 +142,7 @@ describe('InstantSearch', () => {
...DEFAULT_PROPS,
algoliaClient: {},
});

expect(ism.updateClient.mock.calls).toHaveLength(1);
});

Expand Down Expand Up @@ -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(
<InstantSearch {...DEFAULT_PROPS}>
<div />
</InstantSearch>
);

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(
<InstantSearch {...DEFAULT_PROPS}>
<div />
</InstantSearch>
);

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: {},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Object {
"indexName": "name",
"onSearchParameters": undefined,
"onSearchStateChange": undefined,
"refresh": false,
"resultsState": undefined,
"root": Object {
"Root": "div",
Expand Down
6 changes: 6 additions & 0 deletions packages/react-instantsearch/src/core/createInstantSearch.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down Expand Up @@ -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}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ export default function createInstantSearchManager({
search();
}

function clearCache() {
helper.clearCache();
search();
}

function getMetadata(state) {
return widgetsManager
.getWidgets()
Expand Down Expand Up @@ -292,6 +297,7 @@ export default function createInstantSearchManager({
onSearchForFacetValues,
updateClient,
updateIndex,
clearCache,
skipSearch,
};
}
130 changes: 130 additions & 0 deletions stories/RefreshCache.stories.js
Original file line number Diff line number Diff line change
@@ -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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is that normal syntax? What is module?

Copy link
Member Author

@marielaures marielaures Nov 17, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, normal syntax, see https://storybook.js.org/basics/guide-react/#create-the-config-file (then section about writing stories)


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 (
<InstantSearch
appId="latency"
apiKey="6be0576ff61c053d5f9a3225e2a90f76"
indexName="ikea"
refresh={this.state.refresh}
onSearchStateChange={this.onSearchStateChange}
>
<div>
<h2
style={{
color: '#182359',
font: 'Raleway',
size: '30px',
lineHeight: '1',
fontWeight: '200',
}}
>
Feature: Refresh cache
</h2>
<p style={{ color: '#182359', font: 'open sans' }}>
Adding the refresh prop to your InstantSearch component gives you
the possibility to refresh the cache.
</p>

<h3
style={{
color: '#182359',
font: 'Raleway',
size: '18px',
lineHeight: '1',
fontWeight: 'bold',
}}
>
How to test it?
</h3>
<div style={{ color: '#182359', font: 'open sans' }}>
By default, the &apos;refresh&apos; prop is disabled. You will need
to open your network tab in the developer tools.
<ol>
<li>
Type a query in the SearchBox (for instance &apos;clock&apos;).
You should see 5 requests to Algolia (one per letter)
</li>
<li>
Type &apos;clock&apos; again, you will see that no additional
query is made since the results are retrieved from the cache
</li>
<li>
Make sure the SearchBox is empty, click on the &apos;Refresh
cache&apos; button (you should see that Refresh set to true in
the info box below the SearchBox)
</li>
<li>
Type your previous query again: the cache has been cleared and
you will see new requests made to Algolia
</li>
</ol>
</div>
</div>
<hr />
<SearchBox
translations={{
placeholder: 'Search our furnitures: chairs, tables etc.',
}}
/>
<button
onClick={this.refresh}
style={{
borderRadius: '4px',
padding: '10px',
border: 'none',
fontSize: '12px',
cursor: 'pointer',
color: '#fff',
background: '#3369e7',
}}
>
Refresh cache
</button>
<button
style={{
borderRadius: '2px',
padding: '10px',
marginTop: '15px',
border: 'none',
fontSize: '12px',
color: '#999999',
background: '#F3F3F3',
display: 'block',
}}
disabled
>
Refresh is set to: <em>{displayRefresh}</em>
</button>

<CustomHits />
</InstantSearch>
);
}
}

stories.add('with a refresh button', () => <AppWithRefresh />);
Copy link
Contributor

@Haroenv Haroenv Nov 17, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add the minimal jsx:

<InstantSearch
  appId="latency"
  apiKey="6be0576ff61c053d5f9a3225e2a90f76"
  indexName="ikea"
  refresh={this.state.refresh}
  onSearchStateChange={() => this.setState({ refresh: false })}
>
  <SearchBox />
  <button
    onClick={() => this.setState(({ refresh }) => ({ refresh: !refresh }))}
  >
    Refresh cache
  </button>
  <p>
    Refresh is set to: <em>{displayRefresh}</em>
  </p>
  <Hits />
</InstantSearch>

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This story contains a customHit component, that's why I didn't add the JSX story (see thread on the PR to add the JSX addon)

2 changes: 1 addition & 1 deletion stories/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -193,4 +193,4 @@ const displayName = element => {

const filterProps = ['linkedStoryGroup', 'hasPlayground'];

export { displayName, filterProps, Wrap, WrapWithHits };
export { CustomHits, displayName, filterProps, Wrap, WrapWithHits };