Skip to content
This repository has been archived by the owner on Jul 8, 2023. It is now read-only.

Commit

Permalink
➕ add throttleHandler
Browse files Browse the repository at this point in the history
  • Loading branch information
Kir Belevich committed Aug 1, 2017
1 parent 6173c5e commit aebe110
Show file tree
Hide file tree
Showing 7 changed files with 315 additions and 4 deletions.
19 changes: 19 additions & 0 deletions packages/throttle-handler/demo/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from 'react';
import { compose, withState, withHandlers } from 'recompose';

import throttleHandler from '../src/';

const Demo = ({ count, onButtonClick }) => (
<div>
<h1>{count}</h1>
<button onClick={onButtonClick}>CLICK ME FAST</button>
</div>
);

export default compose(
withState('count', 'setCount', 0),
withHandlers({
onButtonClick: ({ count, setCount }) => () => setCount(count + 1)
}),
throttleHandler('onButtonClick', 300)
)(Demo);
28 changes: 28 additions & 0 deletions packages/throttle-handler/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"name": "@hocs/throttle-handler",
"library": "throttleHandler",
"version": "0.1.0",
"description": "Throttle handler HOC for React",
"keywords": [
"react",
"hoc",
"recompose"
],
"main": "lib/index.js",
"module": "es/index.js",
"files": [
"dist/",
"es/",
"lib/"
],
"repository": "deepsweet/hocs",
"author": "Kir Belevich <kir@belevi.ch> (https://github.com/deepsweet)",
"license": "MIT",
"dependencies": {
"just-throttle": "^1.0.2"
},
"peerDependencies": {
"react": "^15.6.1",
"recompose": "^0.24.0"
}
}
41 changes: 41 additions & 0 deletions packages/throttle-handler/src/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React, { Component } from 'react';
import { setDisplayName, wrapDisplayName } from 'recompose';
import throttle from 'just-throttle';

const throttleHandler = (handlerName, interval, leadingCall) => (Target) => {
class ThrottleHandler extends Component {
constructor(props, context) {
super(props, context);

const throttled = throttle(props[handlerName], interval, leadingCall);

this[handlerName] = (e, ...rest) => {
if (e && typeof e.persist === 'function') {
e.persist();
}

return throttled(e, ...rest);
};
}

render() {
const newProps = {
...this.props,
[handlerName]: this[handlerName]
};

return (
<Target {...newProps}/>
);
}
}

if (process.env.NODE_ENV !== 'production') {
return setDisplayName(wrapDisplayName(Target, 'throttleHandler'))(ThrottleHandler);
}

return ThrottleHandler;

};

export default throttleHandler;
38 changes: 38 additions & 0 deletions packages/throttle-handler/test/__snapshots__/index.jsx.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`throttleHandler should throttle handler with \`interval\` option 1`] = `
Array [
Array [
"a",
],
Array [
"d",
],
Array [
"f",
],
]
`;

exports[`throttleHandler should throttle handler with \`leadingCall\` option 1`] = `
Array [
Array [
"a",
],
Array [
"a",
],
Array [
"d",
],
Array [
"d",
],
Array [
"f",
],
Array [
"f",
],
]
`;
143 changes: 143 additions & 0 deletions packages/throttle-handler/test/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import React from 'react';
import { mount } from 'enzyme';

import throttleHandler from '../src/';

const Target = () => null;

describe('throttleHandler', () => {
it('should pass handler arguments through', (done) => {
const EnchancedTarget = throttleHandler('testHandler')(Target);
const mockTestHandler = jest.fn();
const wrapper = mount(
<EnchancedTarget testHandler={mockTestHandler}/>
);
const testHandler = wrapper.find(Target).prop('testHandler');

testHandler('a', 'b', 'c');
setTimeout(() => {
expect(mockTestHandler).toHaveBeenCalledTimes(1);
expect(mockTestHandler).toHaveBeenCalledWith('a', 'b', 'c');
done();
});
});

it('should call `e.persist()` if it has been passed', (done) => {
const EnchancedTarget = throttleHandler('testHandler')(Target);
const mockTestHandler = jest.fn();
const mockPersist = jest.fn();
const wrapper = mount(
<EnchancedTarget testHandler={mockTestHandler}/>
);
const testHandler = wrapper.find(Target).prop('testHandler');

testHandler({ persist: mockPersist });
setTimeout(() => {
expect(mockTestHandler).toHaveBeenCalledTimes(1);
expect(mockPersist).toHaveBeenCalledTimes(1);
done();
}, 0);
});

it('should throttle handler with `interval` option', (done) => {
const EnchancedTarget = throttleHandler('testHandler', 50)(Target);
const mockTestHandler = jest.fn();
const wrapper = mount(
<EnchancedTarget testHandler={mockTestHandler}/>
);
const testHandler = wrapper.find(Target).prop('testHandler');

testHandler('a');

setTimeout(() => {
testHandler('b');

setTimeout(() => {
testHandler('c');

setTimeout(() => {
testHandler('d');

setTimeout(() => {
testHandler('e');

setTimeout(() => {
testHandler('f');

setTimeout(() => {
expect(mockTestHandler.mock.calls).toMatchSnapshot();
done();
}, 50);
}, 50);
}, 20);
}, 20);
}, 20);
}, 20);
});

it('should throttle handler with `leadingCall` option', (done) => {
const EnchancedTarget = throttleHandler('testHandler', 50, true)(Target);
const mockTestHandler = jest.fn();
const wrapper = mount(
<EnchancedTarget testHandler={mockTestHandler}/>
);
const testHandler = wrapper.find(Target).prop('testHandler');

testHandler('a');

setTimeout(() => {
testHandler('b');

setTimeout(() => {
testHandler('c');

setTimeout(() => {
testHandler('d');

setTimeout(() => {
testHandler('e');

setTimeout(() => {
testHandler('f');

setTimeout(() => {
expect(mockTestHandler.mock.calls).toMatchSnapshot();
done();
}, 50);
}, 50);
}, 20);
}, 20);
}, 20);
}, 20);
});

describe('display name', () => {
const origNodeEnv = process.env.NODE_ENV;

afterAll(() => {
process.env.NODE_ENV = origNodeEnv;
});

it('should wrap display name in non-production env', () => {
process.env.NODE_ENV = 'test';

const EnchancedTarget = throttleHandler()(Target);
const wrapper = mount(
<EnchancedTarget/>
);

expect(wrapper.name()).toBe('throttleHandler(Target)');
});

it('should not wrap display name in production env', () => {
process.env.NODE_ENV = 'production';

const EnchancedTarget = throttleHandler()(Target);
const wrapper = mount(
<EnchancedTarget/>
);

expect(wrapper.name()).toBe('ThrottleHandler');
});
});
});
7 changes: 7 additions & 0 deletions packages/throttle-handler/yarn.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1


just-throttle@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/just-throttle/-/just-throttle-1.0.2.tgz#89bb6d3303771ff18c58af9446d25d12ddc9ebd5"
43 changes: 39 additions & 4 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ A collection of [Higher-Order Components](https://facebook.github.io/react/docs/
* :non-potable_water: [`omitProps`](#non-potable_water-omitprops--)
* :recycle: [`withLifecycle`](#recycle-withlifecycle--)
* :hourglass: [`debounceHandler`](#hourglass-debouncehandler--)
* :hourglass: [`throttleHandler`](#hourglass-throttlehandler)
* :hourglass: [`throttleHandler`](#hourglass-throttlehandler--)
* [Development](#development)

## Packages
Expand Down Expand Up @@ -162,17 +162,52 @@ export default compose(
yarn start demo debounce-handler
```
### :hourglass: `throttleHandler`
### :hourglass: [`throttleHandler`](packages/throttle-handler) [![npm](https://img.shields.io/npm/v/@hocs/throttle-handler.svg?style=flat-square)](https://www.npmjs.com/package/@hocs/throttle-handler) [![deps](https://david-dm.org/deepsweet/hocs.svg?path=packages/throttle-handler&style=flat-square)](https://david-dm.org/deepsweet/hocs?path=packages/throttle-handler)
Coming soon.
```
yarn add recompose @hocs/throttle-handler
```
```js
throttleHandler(
handlerName: string,
interval: ?number,
leadingCall: ?boolean
): HigherOrderComponent
```
```js
import React from 'react';
import { compose, withState, withHandlers } fgrom 'recompose';
import throttleHandler from '@hocs/throttle-handler';

const Demo = ({ count, onButtonClick }) => (
<div>
<h1>{count}</h1>
<button onClick={onButtonClick}>CLICK ME FAST</button>
</div>
);

export default compose(
withState('count', 'setCount', 0),
withHandlers({
onButtonClick: ({ count, setCount }) => () => setCount(count + 1)
}),
throttleHandler('onButtonClick', 300)
)(Demo);
```
```
yarn start demo throttle-handler
```
## Development
1. See how existing HOCs are done, especially their `package.json` files.
2. Create a new folder in `packages/`, let's say `with-foo`.
3. Put source code in `with-foo/src/`, it will be transpiled and bundled into `with-foo/dist/`, `with-foo/lib/` and `with-foo/es/`.
4. Put tests written with Jest in `with-foo/test/`.
5. Put demo in `with-foo/demo/`, it will be rendered and wrapped with HMR automatically.
5. Put demo in `with-foo/demo/`, it will be rendered and wrapped with HMR.
6. See [Start](https://github.com/start-runner/start).
6. Done.
Expand Down

0 comments on commit aebe110

Please sign in to comment.