Skip to content

Commit

Permalink
Closes #1: Added debounceWait options for the component
Browse files Browse the repository at this point in the history
- Created utility lib to do the job, based on the underscore debounce
- Added some dependencies for better testing
- Tested the debounce! 👍
  • Loading branch information
rubenspgcavalcante committed Feb 23, 2017
1 parent 4df851f commit 1581791
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 9 deletions.
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,13 @@ Finally, omit the `width` property on your component and wrap it in `AutoWidth`:
</AutoWidth>
~~~

Optionally, you can set up the debouncing rate in milliseconds (default is 100)
~~~ jsx
<AutoWidth className="responsive" debounceWait={150}>
<D3.BarChart />
</AutoWidth>
~~~

## Issues/Contributing

This project welcomes contributions from the community. Here are some issues and areas where we could use some help:
Expand Down
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
"description": "Automatically sets width property on child components",
"main": "dist/react-automatic-width.js",
"scripts": {
"test": "mocha --compilers js:babel-register test/*.test.js",
"test": "mocha --compilers js:babel-register test/{**,.}/*.test.js",
"lint": "eslint src/react-automatic-width.jsx",
"build": "webpack",
"test-coverage": "babel-node ./node_modules/.bin/babel-istanbul cover _mocha -- --compilers js:babel-register test/*.test.js && cat ./coverage/lcov.info | coveralls",
"test-coverage": "babel-node ./node_modules/.bin/babel-istanbul cover _mocha -- --compilers js:babel-register test/{**,.}/*.test.js && cat ./coverage/lcov.info | coveralls",
"prepublish": "npm test && npm run-script build"
},
"repository": {
Expand Down Expand Up @@ -41,6 +41,8 @@
"babel-preset-stage-2": "^6.3.13",
"babel-register": "^6.4.3",
"chai": "^3.4.1",
"chai-spies": "^0.7.1",
"sinon": "^1.17.7",
"coveralls": "^2.11.6",
"domino": "^1.0.21",
"eslint": "^1.7.3",
Expand Down
22 changes: 15 additions & 7 deletions src/react-automatic-width.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import React from 'react';
import React, {PropTypes} from 'react';
import debounce from './utils/debounce';

export default class AutomaticWidth extends React.Component {
static displayName = 'AutomaticWidth';

static propTypes = {
debounceWait: PropTypes.number
};

class AutomaticWidth extends React.Component {
constructor() {
super();
this.state = {
Expand All @@ -9,7 +16,7 @@ class AutomaticWidth extends React.Component {
};
}

_resizeHandler() {
_resizeHandler() {
let dom = this.refs.autowidthWrapper,
{clientWidth} = dom;
if (clientWidth !== this.state.width && clientWidth > 0) {
Expand All @@ -20,7 +27,10 @@ class AutomaticWidth extends React.Component {
}

componentDidMount() {
let boundListener = this._resizeHandler.bind(this);
const {debounceWait} = this.props;
const DEBOUNCE_WAIT = 100;

const boundListener = debounce(this._resizeHandler.bind(this), debounceWait || DEBOUNCE_WAIT, true);
boundListener();
window.addEventListener('resize', boundListener);
this.setState({
Expand All @@ -33,13 +43,11 @@ class AutomaticWidth extends React.Component {
}

render() {
var {width} = this.state;
const {width} = this.state;
return <div ref='autowidthWrapper' {...this.props}>
{React.Children.map(
this.props.children,
c => React.cloneElement(c, {width}))}
</div>;
}
}
AutomaticWidth.displayName = 'AutomaticWidth';
export default AutomaticWidth;
49 changes: 49 additions & 0 deletions src/utils/debounce.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/**
* Returns a function that will not be triggered after right after a call, waiting ${wait} seconds
* to be able to invoke it again. If ${immediate}, it will call again in the leading edge, instead of trailing.
* It's a adapted version of {@link http://underscorejs.org/#debounce}
* @param {Function} func
* @param {number} wait
* @param {boolean} [immediate=false]
* @returns {Function} denounced
*/
export default function debounce(func, wait, immediate = false) {
let timeout, timestamp, context, args, result;

const later = () => {
const last = Date.now() - timestamp;

if (last < wait && last >= 0) {
timeout = setTimeout(later, wait - last);
}
else {
timeout = null;
if (!immediate) {
result = func.apply(context, args);
if (!timeout) {
context = args = null;
}
}
}

};

return function (..._args) {
const callNow = immediate && !timeout;

context = this;
args = _args;
timestamp = Date.now();

if (!timeout) {
timeout = setTimeout(later, wait);
}

if (callNow) {
result = func.apply(context, args);
context = args = null;
}

return result;
};
}
43 changes: 43 additions & 0 deletions test/utils/debounce.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import chai, {expect} from 'chai';
import sinon from 'sinon';
import spies from 'chai-spies';

import debounce from '../../src/utils/debounce';

describe('utils/debounce', () => {
chai.use(spies);
let clock, spy;

beforeEach(() => {
clock = sinon.useFakeTimers();
spy = chai.spy(function () {
});
});

afterEach(() => {
clock.restore();
});

it('Should block any calls before wait time is not finished', () => {
const debounced = debounce(spy, 100, true);
debounced(); // First call

clock.tick(50);
debounced(); //should not call

clock.tick(100);
debounced(); //should call again

expect(spy).to.have.been.called.twice;
});

it("Should delay the call if not immediate", () => {
const debounced = debounce(spy, 100);
debounced(); // Should not call yet

clock.tick(101);
debounced(); //Call

expect(spy).to.have.been.called.once;
});
});

0 comments on commit 1581791

Please sign in to comment.