Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions .size-snapshot.json
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
{
"dist/react-powerplug.umd.js": {
"bundled": 21989,
"minified": 8724,
"gzipped": 2360
"bundled": 26530,
"minified": 10845,
"gzipped": 2674
},
"dist/react-powerplug.cjs.js": {
"bundled": 19846,
"minified": 9915,
"gzipped": 2384
"bundled": 24109,
"minified": 12204,
"gzipped": 2741
},
"dist/react-powerplug.esm.js": {
"bundled": 19241,
"minified": 9402,
"gzipped": 2250,
"bundled": 23454,
"minified": 11645,
"gzipped": 2603,
"treeshaked": {
"rollup": {
"code": 197,
"import_statements": 197
},
"webpack": {
"code": 1495
"code": 1867
}
}
}
Expand Down
50 changes: 50 additions & 0 deletions docs/components/Debounce.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
---
name: Debounce
menu: 4. Utils Containers
---

import { Props, Prop, ChildrenProps, ChildrenProp } from '../_ui/PropsTable'
import { Debounce } from '../../dist/react-powerplug.esm'

# Debounce

The Debounce component delays a function call until it has not been called for the configured wait time.

## Usage

```js
import { Debounce } from 'react-powerplug'
```

```jsx
<Debounce fn={value => console.log(value)} wait={200}>
{({ fn }) => (
<input onChange={event => fn(event.target.value)} />
)}
</Debounce>
```

## Props

<Props>
<Prop name="fn" type="function">
Function to debounce. It can also be provided as `method`.
</Prop>
<Prop name="wait" type="number" default={0}>
Debounce wait time in milliseconds. It can also be provided as `timer`.
</Prop>
<Prop name="children" type="function">
Receive helpers as function. It can also be `render` prop.
</Prop>
</Props>

## Children Props

<ChildrenProps>
<ChildrenProp name="fn" type="function">
Debounced function.
</ChildrenProp>
<ChildrenProp name="cancel" type="function">
Cancel the pending debounced call.
</ChildrenProp>
</ChildrenProps>
50 changes: 50 additions & 0 deletions docs/components/Throttle.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
---
name: Throttle
menu: 4. Utils Containers
---

import { Props, Prop, ChildrenProps, ChildrenProp } from '../_ui/PropsTable'
import { Throttle } from '../../dist/react-powerplug.esm'

# Throttle

The Throttle component limits a function to run at most once per configured wait time.

## Usage

```js
import { Throttle } from 'react-powerplug'
```

```jsx
<Throttle fn={value => console.log(value)} wait={200}>
{({ fn }) => (
<input onChange={event => fn(event.target.value)} />
)}
</Throttle>
```

## Props

<Props>
<Prop name="fn" type="function">
Function to throttle. It can also be provided as `method`.
</Prop>
<Prop name="wait" type="number" default={0}>
Throttle wait time in milliseconds. It can also be provided as `timer`.
</Prop>
<Prop name="children" type="function">
Receive helpers as function. It can also be `render` prop.
</Prop>
</Props>

## Children Props

<ChildrenProps>
<ChildrenProp name="fn" type="function">
Throttled function.
</ChildrenProp>
<ChildrenProp name="cancel" type="function">
Cancel the pending throttled call.
</ChildrenProp>
</ChildrenProps>
2 changes: 0 additions & 2 deletions src/components/Active.js

Large diffs are not rendered by default.

2 changes: 0 additions & 2 deletions src/components/Compose.js

Large diffs are not rendered by default.

2 changes: 0 additions & 2 deletions src/components/Counter.js

Large diffs are not rendered by default.

55 changes: 55 additions & 0 deletions src/components/Debounce.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { Component } from 'react'
import renderProps from '../utils/renderProps'

const noop = () => {}

const getFn = ({ fn, method }) => fn || method || noop

const getWait = ({ wait, timer }) => {
const value = wait != null ? wait : timer
return Number.isFinite(value) ? Math.max(0, value) : 0
}

class Debounce extends Component {
timeoutId = undefined
args = undefined

_clearTimeoutIfNecessary = () => {
if (this.timeoutId !== undefined) {
clearTimeout(this.timeoutId)
this.timeoutId = undefined
}
}

cancel = () => {
this._clearTimeoutIfNecessary()
this.args = undefined
}

fn = (...args) => {
this.args = args
this._clearTimeoutIfNecessary()

this.timeoutId = setTimeout(() => {
const currentArgs = this.args

this.timeoutId = undefined
this.args = undefined

getFn(this.props)(...currentArgs)
}, getWait(this.props))
}

componentWillUnmount() {
this.cancel()
}

render() {
return renderProps(this.props, {
fn: this.fn,
cancel: this.cancel,
})
}
}

export default Debounce
2 changes: 0 additions & 2 deletions src/components/Field.js

Large diffs are not rendered by default.

2 changes: 0 additions & 2 deletions src/components/Focus.js

Large diffs are not rendered by default.

2 changes: 0 additions & 2 deletions src/components/FocusManager.js

Large diffs are not rendered by default.

2 changes: 0 additions & 2 deletions src/components/Form.js

Large diffs are not rendered by default.

2 changes: 0 additions & 2 deletions src/components/Hover.js

Large diffs are not rendered by default.

2 changes: 0 additions & 2 deletions src/components/Interval.js

Large diffs are not rendered by default.

2 changes: 0 additions & 2 deletions src/components/List.js

Large diffs are not rendered by default.

2 changes: 0 additions & 2 deletions src/components/Map.js

Large diffs are not rendered by default.

2 changes: 0 additions & 2 deletions src/components/Set.js

Large diffs are not rendered by default.

2 changes: 0 additions & 2 deletions src/components/State.js

Large diffs are not rendered by default.

72 changes: 72 additions & 0 deletions src/components/Throttle.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { Component } from 'react'
import renderProps from '../utils/renderProps'

const noop = () => {}

const getFn = ({ fn, method }) => fn || method || noop

const getWait = ({ wait, timer }) => {
const value = wait != null ? wait : timer
return Number.isFinite(value) ? Math.max(0, value) : 0
}

class Throttle extends Component {
timeoutId = undefined
trailingArgs = undefined
throttled = false

_clearTimeoutIfNecessary = () => {
if (this.timeoutId !== undefined) {
clearTimeout(this.timeoutId)
this.timeoutId = undefined
}
}

_setTimeout = () => {
this.timeoutId = setTimeout(this._onTimeout, getWait(this.props))
}

_onTimeout = () => {
if (this.trailingArgs) {
const currentArgs = this.trailingArgs

this.trailingArgs = undefined
getFn(this.props)(...currentArgs)
this._setTimeout()
return
}

this.timeoutId = undefined
this.throttled = false
}

cancel = () => {
this._clearTimeoutIfNecessary()
this.trailingArgs = undefined
this.throttled = false
}

fn = (...args) => {
if (this.throttled) {
this.trailingArgs = args
return
}

this.throttled = true
getFn(this.props)(...args)
this._setTimeout()
}

componentWillUnmount() {
this.cancel()
}

render() {
return renderProps(this.props, {
fn: this.fn,
cancel: this.cancel,
})
}
}

export default Throttle
2 changes: 0 additions & 2 deletions src/components/Toggle.js

Large diffs are not rendered by default.

2 changes: 0 additions & 2 deletions src/components/Touch.js

Large diffs are not rendered by default.

2 changes: 0 additions & 2 deletions src/components/Value.js

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export { default as Active } from './components/Active'
export { default as Compose } from './components/Compose'
export { default as Counter } from './components/Counter'
export { default as Debounce } from './components/Debounce'
export { default as Field } from './components/Field'
export { default as Focus } from './components/Focus'
export { default as unstable_FocusManager } from './components/FocusManager'
Expand All @@ -11,6 +12,7 @@ export { default as List } from './components/List'
export { default as Map } from './components/Map'
export { default as Set } from './components/Set'
export { default as State } from './components/State'
export { default as Throttle } from './components/Throttle'
export { default as Toggle } from './components/Toggle'
export { default as Touch } from './components/Touch'
export { default as Value } from './components/Value'
Expand Down
54 changes: 54 additions & 0 deletions src/index.js.flow
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,33 @@ declare export var Counter: React.ComponentType<
| {| initial?: number, onChange?: CounterChange, children: CounterRender |}
>

/* Debounce */

type DebounceFn = (...args: Array<any>) => void

type DebounceRender = ({|
fn: DebounceFn,
cancel: () => void,
|}) => React.Node

type DebounceProps =
| {|
fn?: DebounceFn,
method?: DebounceFn,
wait?: ?number,
timer?: ?number,
render: DebounceRender,
|}
| {|
fn?: DebounceFn,
method?: DebounceFn,
wait?: ?number,
timer?: ?number,
children: DebounceRender,
|}

declare export class Debounce extends React.Component<DebounceProps> {}

/* Focus */

type FocusChange = (focused: boolean) => void
Expand Down Expand Up @@ -230,6 +257,33 @@ type StateProps<T> =

declare export class State<T: Object> extends React.Component<StateProps<T>> {}

/* Throttle */

type ThrottleFn = (...args: Array<any>) => void

type ThrottleRender = ({|
fn: ThrottleFn,
cancel: () => void,
|}) => React.Node

type ThrottleProps =
| {|
fn?: ThrottleFn,
method?: ThrottleFn,
wait?: ?number,
timer?: ?number,
render: ThrottleRender,
|}
| {|
fn?: ThrottleFn,
method?: ThrottleFn,
wait?: ?number,
timer?: ?number,
children: ThrottleRender,
|}

declare export class Throttle extends React.Component<ThrottleProps> {}

/* Interval */

type IntervalRender = ({|
Expand Down
2 changes: 0 additions & 2 deletions src/utils/compose.js

Large diffs are not rendered by default.

2 changes: 0 additions & 2 deletions src/utils/composeEvents.js

Large diffs are not rendered by default.

2 changes: 0 additions & 2 deletions src/utils/renderProps.js

Large diffs are not rendered by default.

42 changes: 42 additions & 0 deletions tests/components/Debounce.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import * as React from 'react'
import TestRenderer from 'react-test-renderer'
import { Debounce } from '../../src'
import { lastCallArg } from './utils'

jest.useFakeTimers()

test('<Debounce />', () => {
const renderFn = jest.fn().mockReturnValue(null)
const method = jest.fn()
const nextMethod = jest.fn()
const renderer = TestRenderer.create(
<Debounce method={method} timer={500}>
{renderFn}
</Debounce>
)

expect(renderFn).toBeCalledTimes(1)

lastCallArg(renderFn).fn('first')
lastCallArg(renderFn).fn('second')

jest.advanceTimersByTime(499)
expect(method).not.toBeCalled()

jest.advanceTimersByTime(1)
expect(method).toBeCalledTimes(1)
expect(method).toBeCalledWith('second')

renderer.update(<Debounce fn={nextMethod} wait={200} render={renderFn} />)
lastCallArg(renderFn).fn('third')
lastCallArg(renderFn).cancel()

jest.advanceTimersByTime(200)
expect(nextMethod).not.toBeCalled()

lastCallArg(renderFn).fn('fourth')
renderer.unmount()

jest.advanceTimersByTime(200)
expect(nextMethod).not.toBeCalled()
})
Loading