Skip to content

Commit 5efaac1

Browse files
author
vvo
committed
perf(React, widgets): implement shouldComponentUpdate, reduce bind
changes: - stop computing static things in every render() call - avoid using bind and creating too much function references - leverage static properties (no bind) and use shouldComponentUpdate were relevant
1 parent b056554 commit 5efaac1

File tree

50 files changed

+927
-688
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+927
-688
lines changed

src/components/ClearAll/ClearAll.js

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,15 @@ import Template from '../Template.js';
33
import {isSpecialClick} from '../../lib/utils.js';
44

55
class ClearAll extends React.Component {
6+
componentWillMount() {
7+
this.handleClick = this.handleClick.bind(this);
8+
}
9+
10+
shouldComponentUpdate(nextProps) {
11+
return this.props.url !== nextProps.url ||
12+
this.props.hasRefinements !== nextProps.hasRefinements;
13+
}
14+
615
handleClick(e) {
716
if (isSpecialClick(e)) {
817
// do not alter the default browser behavior
@@ -14,16 +23,15 @@ class ClearAll extends React.Component {
1423
}
1524

1625
render() {
17-
const className = this.props.cssClasses.link;
1826
const data = {
1927
hasRefinements: this.props.hasRefinements
2028
};
2129

2230
return (
2331
<a
24-
className={className}
32+
className={this.props.cssClasses.link}
2533
href={this.props.url}
26-
onClick={this.handleClick.bind(this)}
34+
onClick={this.handleClick}
2735
>
2836
<Template
2937
data={data}

src/components/CurrentRefinedValues/CurrentRefinedValues.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,13 @@ import Template from '../Template.js';
55
import {isSpecialClick} from '../../lib/utils.js';
66
import map from 'lodash/collection/map';
77
import cloneDeep from 'lodash/lang/cloneDeep';
8+
import {isEqual} from 'lodash';
89

910
class CurrentRefinedValues extends React.Component {
11+
shouldComponentUpdate(nextProps) {
12+
return !isEqual(this.props.refinements, nextProps.refinements);
13+
}
14+
1015
_clearAllElement(position, requestedPosition) {
1116
if (requestedPosition !== position) {
1217
return undefined;

src/components/Hits.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,15 @@ import map from 'lodash/collection/map';
33

44
import Template from './Template.js';
55

6+
import {isEqual} from 'lodash';
7+
68
class Hits extends React.Component {
9+
shouldComponentUpdate(nextProps) {
10+
return this.props.results.hits.length === 0 ||
11+
this.props.results.hits.length !== nextProps.results.hits.length ||
12+
!isEqual(this.props.results.hits, nextProps.results.hits);
13+
}
14+
715
renderWithResults() {
816
let renderedHits = map(this.props.results.hits, hit => {
917
return (

src/components/Pagination/Pagination.js

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,10 @@ import cx from 'classnames';
1111
class Pagination extends React.Component {
1212
constructor(props) {
1313
super(defaultsDeep(props, Pagination.defaultProps));
14-
}
15-
16-
handleClick(pageNumber, event) {
17-
if (isSpecialClick(event)) {
18-
// do not alter the default browser behavior
19-
// if one special key is down
20-
return;
21-
}
22-
event.preventDefault();
23-
this.props.setCurrentPage(pageNumber);
14+
this.handleClick = this.handleClick.bind(this);
2415
}
2516

2617
pageLink({label, ariaLabel, pageNumber, additionalClassName = null, isDisabled = false, isActive = false, createURL}) {
27-
let handleClick = this.handleClick.bind(this, pageNumber);
28-
2918
let cssClasses = {
3019
item: cx(this.props.cssClasses.item, additionalClassName),
3120
link: cx(this.props.cssClasses.link)
@@ -42,9 +31,10 @@ class Pagination extends React.Component {
4231
<PaginationLink
4332
ariaLabel={ariaLabel}
4433
cssClasses={cssClasses}
45-
handleClick={handleClick}
46-
key={label}
34+
handleClick={this.handleClick}
35+
key={label + pageNumber}
4736
label={label}
37+
pageNumber={pageNumber}
4838
url={url}
4939
/>
5040
);
@@ -113,6 +103,16 @@ class Pagination extends React.Component {
113103
return pages;
114104
}
115105

106+
handleClick(pageNumber, event) {
107+
if (isSpecialClick(event)) {
108+
// do not alter the default browser behavior
109+
// if one special key is down
110+
return;
111+
}
112+
event.preventDefault();
113+
this.props.setCurrentPage(pageNumber);
114+
}
115+
116116
render() {
117117
let pager = new Paginator({
118118
currentPage: this.props.currentPage,

src/components/Pagination/PaginationLink.js

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,22 @@
11
import React from 'react';
22

3+
import {isEqual} from 'lodash';
4+
35
class PaginationLink extends React.Component {
6+
componentWillMount() {
7+
this.handleClick = this.handleClick.bind(this);
8+
}
9+
10+
shouldComponentUpdate(nextProps) {
11+
return !isEqual(this.props, nextProps);
12+
}
13+
14+
handleClick(e) {
15+
this.props.handleClick(this.props.pageNumber, e);
16+
}
17+
418
render() {
5-
let {cssClasses, label, ariaLabel, handleClick, url} = this.props;
19+
let {cssClasses, label, ariaLabel, url} = this.props;
620

721
return (
822
<li className={cssClasses.item}>
@@ -11,7 +25,7 @@ class PaginationLink extends React.Component {
1125
className={cssClasses.link}
1226
dangerouslySetInnerHTML={{__html: label}}
1327
href={url}
14-
onClick={handleClick}
28+
onClick={this.handleClick}
1529
></a>
1630
</li>
1731
);
@@ -32,6 +46,7 @@ PaginationLink.propTypes = {
3246
React.PropTypes.string,
3347
React.PropTypes.number
3448
]).isRequired,
49+
pageNumber: React.PropTypes.number,
3550
url: React.PropTypes.string
3651
};
3752

src/components/Pagination/__tests__/Pagination-test.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ describe('Pagination', () => {
6262
let createURL = sinon.stub().returns('/page');
6363
let out = new Pagination({cssClasses: {}}).pageLink({
6464
label: 'test',
65+
pageNumber: 8,
6566
createURL
6667
});
6768

@@ -70,8 +71,9 @@ describe('Pagination', () => {
7071
ariaLabel={undefined}
7172
cssClasses={{item: '', link: ''}}
7273
handleClick={() => {}}
73-
key="test"
74+
key="test8"
7475
label="test"
76+
pageNumber={8}
7577
url="/page"
7678
/>);
7779
expect(createURL.calledOnce).toBe(true, 'createURL should be called once');
@@ -82,6 +84,7 @@ describe('Pagination', () => {
8284
let out = new Pagination({cssClasses: {}}).pageLink({
8385
label: 'test',
8486
isDisabled: true,
87+
pageNumber: 8,
8588
createURL
8689
});
8790

@@ -90,8 +93,9 @@ describe('Pagination', () => {
9093
ariaLabel={undefined}
9194
cssClasses={{item: '', link: ''}}
9295
handleClick={() => {}}
93-
key="test"
96+
key="test8"
9497
label="test"
98+
pageNumber={8}
9599
url="#"
96100
/>);
97101
expect(createURL.called).toBe(false, 'createURL should not be called');

src/components/PoweredBy/PoweredBy.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import React from 'react';
22

33
class PoweredBy extends React.Component {
4+
shouldComponentUpdate() {
5+
return false;
6+
}
7+
48
render() {
59
return (
610
<div className={this.props.cssClasses.root}>

src/components/PriceRanges/PriceRanges.js

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,18 @@ import React from 'react';
33
import Template from '../Template.js';
44
import PriceRangesForm from './PriceRangesForm.js';
55
import cx from 'classnames';
6+
import {isEqual} from 'lodash';
67

78
class PriceRanges extends React.Component {
9+
componentWillMount() {
10+
this.form = this.getForm();
11+
this.refine = this.refine.bind(this);
12+
}
13+
14+
shouldComponentUpdate(nextProps) {
15+
return !isEqual(this.props.facetValues, nextProps.facetValues);
16+
}
17+
818
getForm() {
919
let labels = {
1020
currency: this.props.currency,
@@ -15,24 +25,16 @@ class PriceRanges extends React.Component {
1525
<PriceRangesForm
1626
cssClasses={this.props.cssClasses}
1727
labels={labels}
18-
refine={this.refine.bind(this)}
28+
refine={this.refine}
1929
/>
2030
);
2131
}
2232

23-
getURLFromFacetValue(facetValue) {
24-
if (!this.props.createURL) {
25-
return '#';
26-
}
27-
return this.props.createURL(facetValue.from, facetValue.to, facetValue.isRefined);
28-
}
29-
3033
getItemFromFacetValue(facetValue) {
3134
let cssClassItem = cx(
3235
this.props.cssClasses.item,
3336
{[this.props.cssClasses.active]: facetValue.isRefined}
3437
);
35-
let url = this.getURLFromFacetValue(facetValue);
3638
let key = facetValue.from + '_' + facetValue.to;
3739
let handleClick = this.refine.bind(this, facetValue.from, facetValue.to);
3840
let data = {
@@ -43,7 +45,7 @@ class PriceRanges extends React.Component {
4345
<div className={cssClassItem} key={key}>
4446
<a
4547
className={this.props.cssClasses.link}
46-
href={url}
48+
href={facetValue.url}
4749
onClick={handleClick}
4850
>
4951
<Template data={data} templateKey="item" {...this.props.templateProps} />
@@ -62,22 +64,20 @@ class PriceRanges extends React.Component {
6264
}
6365

6466
render() {
65-
let form = this.getForm();
6667
return (
6768
<div>
6869
<div className={this.props.cssClasses.list}>
6970
{this.props.facetValues.map(facetValue => {
7071
return this.getItemFromFacetValue(facetValue);
7172
})}
7273
</div>
73-
{form}
74+
{this.form}
7475
</div>
7576
);
7677
}
7778
}
7879

7980
PriceRanges.propTypes = {
80-
createURL: React.PropTypes.func.isRequired,
8181
cssClasses: React.PropTypes.shape({
8282
active: React.PropTypes.string,
8383
button: React.PropTypes.string,

src/components/PriceRanges/PriceRangesForm.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
import React from 'react';
22

33
class PriceRangesForm extends React.Component {
4+
componentWillMount() {
5+
this.handleSubmit = this.handleSubmit.bind(this);
6+
}
7+
8+
shouldComponentUpdate() {
9+
return false;
10+
}
11+
412
getInput(type) {
513
return (
614
<label className={this.props.cssClasses.label}>
@@ -19,7 +27,7 @@ class PriceRangesForm extends React.Component {
1927
render() {
2028
let fromInput = this.getInput('from');
2129
let toInput = this.getInput('to');
22-
let onSubmit = this.handleSubmit.bind(this);
30+
let onSubmit = this.handleSubmit;
2331
return (
2432
<form className={this.props.cssClasses.form} onSubmit={onSubmit} ref="form">
2533
{fromInput}

src/components/PriceRanges/__tests__/PriceRanges-test.js

Lines changed: 2 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -58,46 +58,11 @@ describe('PriceRanges', () => {
5858
stubMethod('render');
5959
});
6060

61-
context('getURLFromFacetValue', () => {
62-
it('should be a # if no createURL method passed', () => {
63-
// Given
64-
let component = getComponentWithMockRendering({
65-
createURL: null
66-
});
67-
68-
// When
69-
let url = component.getURLFromFacetValue();
70-
71-
// Then
72-
expect(url).toEqual('#');
73-
});
74-
it('should call the createURL method passed with the facetValue', () => {
75-
// Given
76-
let mockCreateURL = sinon.spy();
77-
let component = getComponentWithMockRendering({
78-
createURL: mockCreateURL
79-
});
80-
let facetValue = {
81-
from: 6,
82-
to: 8,
83-
isRefined: true
84-
};
85-
86-
// When
87-
component.getURLFromFacetValue(facetValue);
88-
89-
// Then
90-
expect(mockCreateURL.called).toBe(true);
91-
expect(mockCreateURL.calledWith(6, 8, true)).toBe(true);
92-
});
93-
});
94-
9561
context('getItemFromFacetValue', () => {
9662
let props;
9763
let facetValue;
9864

9965
beforeEach(() => {
100-
stubMethod('getURLFromFacetValue', 'url');
10166
props = {
10267
cssClasses: {
10368
item: 'item',
@@ -109,7 +74,8 @@ describe('PriceRanges', () => {
10974
facetValue = {
11075
from: 1,
11176
to: 10,
112-
isRefined: false
77+
isRefined: false,
78+
url: 'url'
11379
};
11480
});
11581

0 commit comments

Comments
 (0)