Skip to content

Commit bca09af

Browse files
author
Alexandre Stanislawski
committed
feat(connector): connectHits (iteration 2)
1 parent 7350c3a commit bca09af

File tree

5 files changed

+101
-107
lines changed

5 files changed

+101
-107
lines changed

src/connectors/hits/__tests__/connectHits-test.js

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
2-
31
import sinon from 'sinon';
42

53
import jsHelper from 'algoliasearch-helper';
@@ -42,9 +40,6 @@ describe('connectHits', () => {
4240
// test if isFirstRendering is true during init
4341
expect(rendering.lastCall.args[1]).toBe(true);
4442

45-
const firstRenderingOptions = rendering.lastCall.args[0];
46-
expect(firstRenderingOptions.containerNode).toBe(container);
47-
4843
widget.render({
4944
results: new SearchResults(helper.state, [{}]),
5045
state: helper.state,
@@ -55,9 +50,6 @@ describe('connectHits', () => {
5550
// test that rendering has been called during init with isFirstRendering = false
5651
expect(rendering.callCount).toBe(2);
5752
expect(rendering.lastCall.args[1]).toBe(false);
58-
59-
const secondRenderingOptions = rendering.lastCall.args[0];
60-
expect(secondRenderingOptions.containerNode).toBe(container);
6153
});
6254

6355
it('Provides the hits and the whole results', () => {

src/connectors/hits/connectHits.js

Lines changed: 6 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,91 +1,21 @@
1-
import {
2-
bemHelper,
3-
prepareTemplateProps,
4-
getContainerNode,
5-
} from '../../lib/utils.js';
6-
import cx from 'classnames';
7-
import defaultTemplates from './defaultTemplates.js';
8-
9-
const bem = bemHelper('ais-hits');
10-
11-
/**
12-
* Display the list of results (hits) from the current search
13-
* @function hits
14-
* @param {string|DOMElement} options.container CSS Selector or DOMElement to insert the widget
15-
* @param {number} [options.hitsPerPage=20] The number of hits to display per page [*]
16-
* @param {Object} [options.templates] Templates to use for the widget
17-
* @param {string|Function} [options.templates.empty=''] Template to use when there are no results.
18-
* @param {string|Function} [options.templates.item=''] Template to use for each result. This template will receive an object containing a single record.
19-
* @param {string|Function} [options.templates.allItems=''] Template to use for the list of all results. (Can't be used with `item` template). This template will receive a complete SearchResults result object, this object contains the key hits that contains all the records retrieved.
20-
* @param {Object} [options.transformData] Method to change the object passed to the templates
21-
* @param {Function} [options.transformData.empty] Method used to change the object passed to the `empty` template
22-
* @param {Function} [options.transformData.item] Method used to change the object passed to the `item` template
23-
* @param {Function} [options.transformData.allItems] Method used to change the object passed to the `allItems` template
24-
* @param {Object} [options.cssClasses] CSS classes to add
25-
* @param {string|string[]} [options.cssClasses.root] CSS class to add to the wrapping element
26-
* @param {string|string[]} [options.cssClasses.empty] CSS class to add to the wrapping element when no results
27-
* @param {string|string[]} [options.cssClasses.item] CSS class to add to each result
28-
* @return {Object}
29-
*/
30-
const usage = `
31-
Usage:
32-
hits({
33-
container,
34-
[ cssClasses.{root,empty,item}={} ],
35-
[ templates.{empty,item} | templates.{empty, allItems} ],
36-
[ transformData.{empty,item} | transformData.{empty, allItems} ],
37-
[ hitsPerPage=20 ]
38-
})`;
391
const connectHits = renderHits => ({
40-
container,
41-
cssClasses: userCssClasses = {},
42-
templates = defaultTemplates,
43-
transformData,
442
hitsPerPage = 20,
45-
} = {}) => {
46-
if (!container) {
47-
throw new Error(`Must provide a container.${usage}`);
48-
}
49-
50-
if (templates.item && templates.allItems) {
51-
throw new Error(`Must contain only allItems OR item template.${usage}`);
52-
}
53-
54-
const containerNode = getContainerNode(container);
55-
const cssClasses = {
56-
root: cx(bem(null), userCssClasses.root),
57-
item: cx(bem('item'), userCssClasses.item),
58-
empty: cx(bem(null, 'empty'), userCssClasses.empty),
59-
};
60-
61-
return {
3+
} = {}) => ({
624
getConfiguration: () => ({hitsPerPage}),
63-
init({templatesConfig}) {
64-
this._templateProps = prepareTemplateProps({
65-
transformData,
66-
defaultTemplates,
67-
templatesConfig,
68-
templates,
69-
});
70-
5+
init({instantSearchInstance}) {
716
renderHits({
72-
cssClasses,
737
hits: [],
748
results: undefined,
75-
templateProps: this._templateProps,
76-
containerNode,
9+
instantSearchInstance,
7710
}, true);
7811
},
79-
render({results}) {
12+
render({results, instantSearchInstance}) {
8013
renderHits({
81-
cssClasses,
8214
hits: results.hits,
8315
results,
84-
templateProps: this._templateProps,
85-
containerNode,
16+
instantSearchInstance,
8617
}, false);
8718
},
88-
};
89-
};
19+
});
9020

9121
export default connectHits;

src/widgets/hits/__tests__/hits-test.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@ import expect from 'expect';
33
import sinon from 'sinon';
44
import expectJSX from 'expect-jsx';
55
expect.extend(expectJSX);
6-
import hits from '../hits';
7-
import Hits from '../../../components/Hits';
8-
import defaultTemplates from '../../../connectors/hits/defaultTemplates.js';
6+
import hits from '../hits.js';
7+
import Hits from '../../../components/Hits.js';
8+
import defaultTemplates from '../defaultTemplates.js';
99

1010
describe('hits call', () => {
1111
it('throws an exception when no container', () => {
12-
expect(hits).toThrow(/^Must provide a container/);
12+
expect(hits).toThrow();
1313
});
1414
});
1515

@@ -33,7 +33,7 @@ describe('hits()', () => {
3333
useCustomCompileOptions: {item: false, empty: false},
3434
};
3535
widget = hits({container, cssClasses: {root: ['root', 'cx']}});
36-
widget.init({});
36+
widget.init({instantSearchInstance: {templateProps}});
3737
results = {hits: [{first: 'hit', second: 'hit'}]};
3838
});
3939

src/widgets/hits/hits.js

Lines changed: 90 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,49 @@ import Hits from '../../components/Hits.js';
44

55
import connectHits from '../../connectors/hits/connectHits.js';
66

7+
import {
8+
bemHelper,
9+
prepareTemplateProps,
10+
getContainerNode,
11+
} from '../../lib/utils.js';
12+
import cx from 'classnames';
13+
import defaultTemplates from './defaultTemplates.js';
14+
15+
const bem = bemHelper('ais-hits');
16+
17+
const renderer = ({
18+
renderState,
19+
cssClasses,
20+
containerNode,
21+
transformData,
22+
templates,
23+
}) => ({
24+
hits, // eslint-disable-line
25+
results,
26+
templateProps,
27+
instantSearchInstance,
28+
}, isFirstRendering) => {
29+
if (isFirstRendering) {
30+
renderState.templateProps = prepareTemplateProps({
31+
transformData,
32+
defaultTemplates,
33+
templatesConfig: instantSearchInstance.templatesConfig,
34+
templates,
35+
});
36+
return;
37+
}
38+
39+
ReactDOM.render(
40+
<Hits
41+
cssClasses={cssClasses}
42+
hits={hits}
43+
results={results}
44+
templateProps={renderState.templateProps}
45+
/>,
46+
containerNode
47+
);
48+
};
49+
750
/**
851
* Display the list of results (hits) from the current search
952
* @function hits
@@ -23,23 +66,52 @@ import connectHits from '../../connectors/hits/connectHits.js';
2366
* @param {string|string[]} [options.cssClasses.item] CSS class to add to each result
2467
* @return {Object}
2568
*/
26-
export default connectHits(defaultRendering);
69+
const usage = `
70+
Usage:
71+
hits({
72+
container,
73+
[ cssClasses.{root,empty,item}={} ],
74+
[ templates.{empty,item} | templates.{empty, allItems} ],
75+
[ transformData.{empty,item} | transformData.{empty, allItems} ],
76+
[ hitsPerPage=20 ]
77+
})`;
2778

28-
function defaultRendering({
29-
cssClasses,
30-
hits,
31-
results,
32-
templateProps,
33-
containerNode,
34-
}, isFirstRendering) {
35-
if (isFirstRendering) return;
36-
ReactDOM.render(
37-
<Hits
38-
cssClasses={cssClasses}
39-
hits={hits}
40-
results={results}
41-
templateProps={templateProps}
42-
/>,
43-
containerNode
44-
);
79+
export default function hits({
80+
container,
81+
cssClasses: userCssClasses = {},
82+
templates = defaultTemplates,
83+
transformData,
84+
hitsPerPage = 20,
85+
}) {
86+
if (!container) {
87+
throw new Error(`Must provide a container.${usage}`);
88+
}
89+
90+
if (templates.item && templates.allItems) {
91+
throw new Error(`Must contain only allItems OR item template.${usage}`);
92+
}
93+
94+
const containerNode = getContainerNode(container);
95+
const cssClasses = {
96+
root: cx(bem(null), userCssClasses.root),
97+
item: cx(bem('item'), userCssClasses.item),
98+
empty: cx(bem(null, 'empty'), userCssClasses.empty),
99+
};
100+
101+
const specializedRenderer = renderer({
102+
containerNode,
103+
cssClasses,
104+
renderState: {},
105+
transformData,
106+
templates,
107+
});
108+
109+
try {
110+
const makeHits = connectHits(specializedRenderer);
111+
return makeHits({
112+
hitsPerPage,
113+
});
114+
} catch (e) {
115+
throw new Error(usage);
116+
}
45117
}

0 commit comments

Comments
 (0)