Skip to content

Commit

Permalink
perf(React, widgets): implement shouldComponentUpdate, reduce bind
Browse files Browse the repository at this point in the history
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
  • Loading branch information
vvo committed Feb 11, 2016
1 parent b056554 commit 5efaac1
Show file tree
Hide file tree
Showing 50 changed files with 927 additions and 688 deletions.
14 changes: 11 additions & 3 deletions src/components/ClearAll/ClearAll.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,15 @@ import Template from '../Template.js';
import {isSpecialClick} from '../../lib/utils.js';

class ClearAll extends React.Component {
componentWillMount() {
this.handleClick = this.handleClick.bind(this);
}

shouldComponentUpdate(nextProps) {
return this.props.url !== nextProps.url ||
this.props.hasRefinements !== nextProps.hasRefinements;
}

handleClick(e) {
if (isSpecialClick(e)) {
// do not alter the default browser behavior
Expand All @@ -14,16 +23,15 @@ class ClearAll extends React.Component {
}

render() {
const className = this.props.cssClasses.link;
const data = {
hasRefinements: this.props.hasRefinements
};

return (
<a
className={className}
className={this.props.cssClasses.link}
href={this.props.url}
onClick={this.handleClick.bind(this)}
onClick={this.handleClick}
>
<Template
data={data}
Expand Down
5 changes: 5 additions & 0 deletions src/components/CurrentRefinedValues/CurrentRefinedValues.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,13 @@ import Template from '../Template.js';
import {isSpecialClick} from '../../lib/utils.js';
import map from 'lodash/collection/map';
import cloneDeep from 'lodash/lang/cloneDeep';
import {isEqual} from 'lodash';

class CurrentRefinedValues extends React.Component {
shouldComponentUpdate(nextProps) {
return !isEqual(this.props.refinements, nextProps.refinements);
}

_clearAllElement(position, requestedPosition) {
if (requestedPosition !== position) {
return undefined;
Expand Down
8 changes: 8 additions & 0 deletions src/components/Hits.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,15 @@ import map from 'lodash/collection/map';

import Template from './Template.js';

import {isEqual} from 'lodash';

class Hits extends React.Component {
shouldComponentUpdate(nextProps) {
return this.props.results.hits.length === 0 ||
this.props.results.hits.length !== nextProps.results.hits.length ||
!isEqual(this.props.results.hits, nextProps.results.hits);
}

renderWithResults() {
let renderedHits = map(this.props.results.hits, hit => {
return (
Expand Down
28 changes: 14 additions & 14 deletions src/components/Pagination/Pagination.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,10 @@ import cx from 'classnames';
class Pagination extends React.Component {
constructor(props) {
super(defaultsDeep(props, Pagination.defaultProps));
}

handleClick(pageNumber, event) {
if (isSpecialClick(event)) {
// do not alter the default browser behavior
// if one special key is down
return;
}
event.preventDefault();
this.props.setCurrentPage(pageNumber);
this.handleClick = this.handleClick.bind(this);
}

pageLink({label, ariaLabel, pageNumber, additionalClassName = null, isDisabled = false, isActive = false, createURL}) {
let handleClick = this.handleClick.bind(this, pageNumber);

let cssClasses = {
item: cx(this.props.cssClasses.item, additionalClassName),
link: cx(this.props.cssClasses.link)
Expand All @@ -42,9 +31,10 @@ class Pagination extends React.Component {
<PaginationLink
ariaLabel={ariaLabel}
cssClasses={cssClasses}
handleClick={handleClick}
key={label}
handleClick={this.handleClick}
key={label + pageNumber}
label={label}
pageNumber={pageNumber}
url={url}
/>
);
Expand Down Expand Up @@ -113,6 +103,16 @@ class Pagination extends React.Component {
return pages;
}

handleClick(pageNumber, event) {
if (isSpecialClick(event)) {
// do not alter the default browser behavior
// if one special key is down
return;
}
event.preventDefault();
this.props.setCurrentPage(pageNumber);
}

render() {
let pager = new Paginator({
currentPage: this.props.currentPage,
Expand Down
19 changes: 17 additions & 2 deletions src/components/Pagination/PaginationLink.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,22 @@
import React from 'react';

import {isEqual} from 'lodash';

class PaginationLink extends React.Component {
componentWillMount() {
this.handleClick = this.handleClick.bind(this);
}

shouldComponentUpdate(nextProps) {
return !isEqual(this.props, nextProps);
}

handleClick(e) {
this.props.handleClick(this.props.pageNumber, e);
}

render() {
let {cssClasses, label, ariaLabel, handleClick, url} = this.props;
let {cssClasses, label, ariaLabel, url} = this.props;

return (
<li className={cssClasses.item}>
Expand All @@ -11,7 +25,7 @@ class PaginationLink extends React.Component {
className={cssClasses.link}
dangerouslySetInnerHTML={{__html: label}}
href={url}
onClick={handleClick}
onClick={this.handleClick}
></a>
</li>
);
Expand All @@ -32,6 +46,7 @@ PaginationLink.propTypes = {
React.PropTypes.string,
React.PropTypes.number
]).isRequired,
pageNumber: React.PropTypes.number,
url: React.PropTypes.string
};

Expand Down
8 changes: 6 additions & 2 deletions src/components/Pagination/__tests__/Pagination-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ describe('Pagination', () => {
let createURL = sinon.stub().returns('/page');
let out = new Pagination({cssClasses: {}}).pageLink({
label: 'test',
pageNumber: 8,
createURL
});

Expand All @@ -70,8 +71,9 @@ describe('Pagination', () => {
ariaLabel={undefined}
cssClasses={{item: '', link: ''}}
handleClick={() => {}}
key="test"
key="test8"
label="test"
pageNumber={8}
url="/page"
/>);
expect(createURL.calledOnce).toBe(true, 'createURL should be called once');
Expand All @@ -82,6 +84,7 @@ describe('Pagination', () => {
let out = new Pagination({cssClasses: {}}).pageLink({
label: 'test',
isDisabled: true,
pageNumber: 8,
createURL
});

Expand All @@ -90,8 +93,9 @@ describe('Pagination', () => {
ariaLabel={undefined}
cssClasses={{item: '', link: ''}}
handleClick={() => {}}
key="test"
key="test8"
label="test"
pageNumber={8}
url="#"
/>);
expect(createURL.called).toBe(false, 'createURL should not be called');
Expand Down
4 changes: 4 additions & 0 deletions src/components/PoweredBy/PoweredBy.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import React from 'react';

class PoweredBy extends React.Component {
shouldComponentUpdate() {
return false;
}

render() {
return (
<div className={this.props.cssClasses.root}>
Expand Down
26 changes: 13 additions & 13 deletions src/components/PriceRanges/PriceRanges.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,18 @@ import React from 'react';
import Template from '../Template.js';
import PriceRangesForm from './PriceRangesForm.js';
import cx from 'classnames';
import {isEqual} from 'lodash';

class PriceRanges extends React.Component {
componentWillMount() {
this.form = this.getForm();
this.refine = this.refine.bind(this);
}

shouldComponentUpdate(nextProps) {
return !isEqual(this.props.facetValues, nextProps.facetValues);
}

getForm() {
let labels = {
currency: this.props.currency,
Expand All @@ -15,24 +25,16 @@ class PriceRanges extends React.Component {
<PriceRangesForm
cssClasses={this.props.cssClasses}
labels={labels}
refine={this.refine.bind(this)}
refine={this.refine}
/>
);
}

getURLFromFacetValue(facetValue) {
if (!this.props.createURL) {
return '#';
}
return this.props.createURL(facetValue.from, facetValue.to, facetValue.isRefined);
}

getItemFromFacetValue(facetValue) {
let cssClassItem = cx(
this.props.cssClasses.item,
{[this.props.cssClasses.active]: facetValue.isRefined}
);
let url = this.getURLFromFacetValue(facetValue);
let key = facetValue.from + '_' + facetValue.to;
let handleClick = this.refine.bind(this, facetValue.from, facetValue.to);
let data = {
Expand All @@ -43,7 +45,7 @@ class PriceRanges extends React.Component {
<div className={cssClassItem} key={key}>
<a
className={this.props.cssClasses.link}
href={url}
href={facetValue.url}
onClick={handleClick}
>
<Template data={data} templateKey="item" {...this.props.templateProps} />
Expand All @@ -62,22 +64,20 @@ class PriceRanges extends React.Component {
}

render() {
let form = this.getForm();
return (
<div>
<div className={this.props.cssClasses.list}>
{this.props.facetValues.map(facetValue => {
return this.getItemFromFacetValue(facetValue);
})}
</div>
{form}
{this.form}
</div>
);
}
}

PriceRanges.propTypes = {
createURL: React.PropTypes.func.isRequired,
cssClasses: React.PropTypes.shape({
active: React.PropTypes.string,
button: React.PropTypes.string,
Expand Down
10 changes: 9 additions & 1 deletion src/components/PriceRanges/PriceRangesForm.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
import React from 'react';

class PriceRangesForm extends React.Component {
componentWillMount() {
this.handleSubmit = this.handleSubmit.bind(this);
}

shouldComponentUpdate() {
return false;
}

getInput(type) {
return (
<label className={this.props.cssClasses.label}>
Expand All @@ -19,7 +27,7 @@ class PriceRangesForm extends React.Component {
render() {
let fromInput = this.getInput('from');
let toInput = this.getInput('to');
let onSubmit = this.handleSubmit.bind(this);
let onSubmit = this.handleSubmit;
return (
<form className={this.props.cssClasses.form} onSubmit={onSubmit} ref="form">
{fromInput}
Expand Down
38 changes: 2 additions & 36 deletions src/components/PriceRanges/__tests__/PriceRanges-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,46 +58,11 @@ describe('PriceRanges', () => {
stubMethod('render');
});

context('getURLFromFacetValue', () => {
it('should be a # if no createURL method passed', () => {
// Given
let component = getComponentWithMockRendering({
createURL: null
});

// When
let url = component.getURLFromFacetValue();

// Then
expect(url).toEqual('#');
});
it('should call the createURL method passed with the facetValue', () => {
// Given
let mockCreateURL = sinon.spy();
let component = getComponentWithMockRendering({
createURL: mockCreateURL
});
let facetValue = {
from: 6,
to: 8,
isRefined: true
};

// When
component.getURLFromFacetValue(facetValue);

// Then
expect(mockCreateURL.called).toBe(true);
expect(mockCreateURL.calledWith(6, 8, true)).toBe(true);
});
});

context('getItemFromFacetValue', () => {
let props;
let facetValue;

beforeEach(() => {
stubMethod('getURLFromFacetValue', 'url');
props = {
cssClasses: {
item: 'item',
Expand All @@ -109,7 +74,8 @@ describe('PriceRanges', () => {
facetValue = {
from: 1,
to: 10,
isRefined: false
isRefined: false,
url: 'url'
};
});

Expand Down
Loading

0 comments on commit 5efaac1

Please sign in to comment.