Skip to content

Commit 250b172

Browse files
jedwards1211renatorib
authored andcommitted
add Timer component (#79)
* feat: add Timer component * rename Timer to Interval * remove delay prop * do not use forceUpdate * update size snapshot
1 parent 8c72825 commit 250b172

File tree

7 files changed

+211
-31
lines changed

7 files changed

+211
-31
lines changed

.size-snapshot.json

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
11
{
22
"dist/react-powerplug.umd.js": {
3-
"bundled": 20071,
4-
"minified": 8068,
5-
"gzipped": 2173
3+
"bundled": 24185,
4+
"minified": 10002,
5+
"gzipped": 2571
66
},
77
"dist/react-powerplug.cjs.js": {
8-
"bundled": 17186,
9-
"minified": 8690,
10-
"gzipped": 2055
8+
"bundled": 21058,
9+
"minified": 10885,
10+
"gzipped": 2423
1111
},
1212
"dist/react-powerplug.esm.js": {
13-
"bundled": 16589,
14-
"minified": 8186,
15-
"gzipped": 1936,
13+
"bundled": 20412,
14+
"minified": 10336,
15+
"gzipped": 2296,
1616
"treeshaked": {
17-
"rollup": 1489,
18-
"webpack": 1927
17+
"rollup": 3115,
18+
"webpack": 3581
1919
}
2020
}
2121
}

README.md

Lines changed: 36 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ import { Pagination, Tabs, Checkbox } from './MyDumbComponents'
5959
</details>
6060

6161
## ⚠️ Master is unstable
62+
6263
> This branch is **unstable** and is in **active development**.
6364
> For the latest stable version go to [0.1-stable branch](https://github.com/renatorib/react-powerplug/tree/0.1-stable)
6465
@@ -67,27 +68,29 @@ import { Pagination, Tabs, Checkbox } from './MyDumbComponents'
6768
> **_Note_** _This is a kind of a cheat sheet for fast search._
6869
> _If you want a more detailed **API Reference** and examples for each component see [full docs](/docs)_
6970
70-
| Component | Component Props | Render Props | |
71-
| ---------------------------- | ----------------------- | ---------------------------------------------- | --------------------------------------------------------------------------- |
71+
| Component | Component Props | Render Props | |
72+
| ---------------------------- | ----------------------- | ---------------------------------------------- | ------------------------------------------------------------------------ |
7273
| <h6>STATE CONTAINERS</h6> |
73-
| **\<State>** | `{ initial, onChange }` | `{ state, setState }` | [:point_down:](#state) [:books:](docs/components/State.md) |
74-
| **\<Toggle>** | `{ initial, onChange }` | `{ on, toggle, set }` | [:point_down:](#toggle) [:books:](docs/components/Toggle.md) |
75-
| **\<Counter>** | `{ initial, onChange }` | `{count, inc, dec, incBy, decBy, set }` | [:point_down:](#counter) [:books:](docs/components/Counter.md) |
76-
| **\<Value>** | `{ initial, onChange }` | `{ value, setValue, set }` | [:point_down:](#value) [:books:](docs/components/Value.md) |
77-
| **\<Map>** | `{ initial, onChange }` | `{ set, get, over, values }` | [:point_down:](#map) [:books:](docs/components/Map.md) |
78-
| **\<Set>** | `{ initial, onChange }` | `{ values, add, clear, remove, has }` | [:point_down:](#set) [:books:](docs/components/Set.md) |
79-
| **\<List>** | `{ initial, onChange }` | `{ list, first, last, push, pull, sort, set }` | [:point_down:](#list) [:books:](docs/components/List.md) |
74+
| **\<State>** | `{ initial, onChange }` | `{ state, setState }` | [:point_down:](#state) [:books:](docs/components/State.md) |
75+
| **\<Toggle>** | `{ initial, onChange }` | `{ on, toggle, set }` | [:point_down:](#toggle) [:books:](docs/components/Toggle.md) |
76+
| **\<Counter>** | `{ initial, onChange }` | `{count, inc, dec, incBy, decBy, set }` | [:point_down:](#counter) [:books:](docs/components/Counter.md) |
77+
| **\<Value>** | `{ initial, onChange }` | `{ value, setValue, set }` | [:point_down:](#value) [:books:](docs/components/Value.md) |
78+
| **\<Map>** | `{ initial, onChange }` | `{ set, get, over, values }` | [:point_down:](#map) [:books:](docs/components/Map.md) |
79+
| **\<Set>** | `{ initial, onChange }` | `{ values, add, clear, remove, has }` | [:point_down:](#set) [:books:](docs/components/Set.md) |
80+
| **\<List>** | `{ initial, onChange }` | `{ list, first, last, push, pull, sort, set }` | [:point_down:](#list) [:books:](docs/components/List.md) |
8081
| <h6>FEEDBACK CONTAINERS</h6> |
81-
| **\<Hover>** | `{ onChange }` | `{ isHovered, bind }` | [:point_down:](#hover) [:books:](docs/components/Hover.md) |
82-
| **\<Active>** | `{ onChange }` | `{ isActive, bind }` | [:point_down:](#active) [:books:](docs/components/Active.md) |
83-
| **\<Focus>** | `{ onChange }` | `{ isFocused, bind }` | [:point_down:](#focus) [:books:](docs/components/Focus.md) |
84-
| **\<Touch>** | `{ onChange }` | `{ isTouched, bind }` | [:point_down:](#touch) [:books:](docs/components/Touch.md) |
85-
| **\<FocusManager>** | `{ onChange }` | `{ isFocused, blur, bind }` | [:point_down:](#focusmanager) [:books:](docs/components/FocusManager.md) |
82+
| **\<Hover>** | `{ onChange }` | `{ isHovered, bind }` | [:point_down:](#hover) [:books:](docs/components/Hover.md) |
83+
| **\<Active>** | `{ onChange }` | `{ isActive, bind }` | [:point_down:](#active) [:books:](docs/components/Active.md) |
84+
| **\<Focus>** | `{ onChange }` | `{ isFocused, bind }` | [:point_down:](#focus) [:books:](docs/components/Focus.md) |
85+
| **\<Touch>** | `{ onChange }` | `{ isTouched, bind }` | [:point_down:](#touch) [:books:](docs/components/Touch.md) |
86+
| **\<FocusManager>** | `{ onChange }` | `{ isFocused, blur, bind }` | [:point_down:](#focusmanager) [:books:](docs/components/FocusManager.md) |
8687
| <h6>FORM CONTAINERS</h6> |
87-
| **\<Input>** | `{ initial, onChange }` | `{ set, value, bind }` | [:point_down:](#input) [:books:](docs/components/Input.md) |
88-
| **\<Form>** | `{ initial, onChange }` | `{ input, values }` | [:point_down:](#form) [:books:](docs/components/Form.md) |
88+
| **\<Input>** | `{ initial, onChange }` | `{ set, value, bind }` | [:point_down:](#input) [:books:](docs/components/Input.md) |
89+
| **\<Form>** | `{ initial, onChange }` | `{ input, values }` | [:point_down:](#form) [:books:](docs/components/Form.md) |
8990
| <h6>OTHER</h6> |
90-
| **\<Compose>** | `{ components }` | _depends on components prop_ | [:point_down:](#composing-components) [:books:](docs/components/Compose.md) |
91+
| (docs/components/Compose.md) |
92+
| **\<Interval>** | `{ delay }` | `{ stop, start, toggle }` | [:point_down:](#interval) [:books:](docs/components/Interval.md) |
93+
| **\<Compose>** | `{ components }` | _depends on components prop_ | [:point_down:](#composing-components) [:books:] |
9194

9295
## Utilities
9396

@@ -281,13 +284,27 @@ import { Pagination, Tabs, Checkbox } from './MyDumbComponents'
281284
<Submit>Send</Submit>
282285

283286
{/*
284-
input(id) => { bind, set, value }
285-
*/}
287+
input(id) => { bind, set, value }
288+
*/}
286289
</form>
287290
)}
288291
</Form>
289292
```
290293
294+
### Interval
295+
296+
```jsx
297+
<Interval delay={1000}>
298+
{({ stop, start }) => (
299+
<>
300+
<div>The time is now {new Date().toLocaleTimeString()}</div>
301+
<button onClick={() => stop()}>Stop interval</button>
302+
<button onClick={() => start()}>Start interval</button>
303+
</>
304+
)}
305+
</Interval>
306+
```
307+
291308
# Composing Components
292309
293310
If you want to avoid 'render props hell' you can compose two or more components in a single one.

docs/components/Interval.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Interval
2+
3+
The Interval component is used for when it's necessary to re-render at regular intervals. Also known as Frame.
4+
5+
```js
6+
import { Interval } from 'react-powerplug'
7+
```
8+
9+
```jsx
10+
<Interval delay={1000}>
11+
{({ start, stop }) => (
12+
<>
13+
<div>The time is now {new Date().toLocaleTimeString()}</div>
14+
<button onClick={() => stop()}>Stop interval</button>
15+
<button onClick={() => resume()}>Start interval</button>
16+
</>
17+
)}
18+
</Interval>
19+
```
20+
21+
## Interval Props
22+
23+
**delay** _(optional)_
24+
Specifies the delay (for `setInterval`) between re-renders in milliseconds.
25+
26+
The interval will be reset any time this prop changes.
27+
28+
Whenever `delay` is not a finite number, no interval will be set and `Interval` will
29+
not automatically re-render.
30+
31+
## Interval Children Props
32+
33+
TL;DR: `{ stop, start, toggle }`
34+
35+
**stop**
36+
`() => void`
37+
Stop (or pause) re-renders intervals
38+
39+
**start**
40+
`(delay?: number) => void`
41+
Start (or resume) re-renders intervals at defined delay (if not passed delay arg it will be used from props). Good way to change delay time when needed.

src/components/Interval.js

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { Component } from 'react'
2+
import renderProps from '../utils/renderProps'
3+
4+
class Interval extends Component {
5+
static defaultProps = {
6+
delay: 1000,
7+
}
8+
9+
state = {
10+
times: 0,
11+
}
12+
13+
intervalId = undefined
14+
15+
_clearIntervalIfNecessary = () => {
16+
if (this.intervalId) {
17+
this.intervalId = clearInterval(this.intervalId)
18+
}
19+
}
20+
21+
_setIntervalIfNecessary = delay => {
22+
if (Number.isFinite(delay)) {
23+
this.intervalId = setInterval(
24+
() => this.setState(s => ({ times: s.times + 1 })),
25+
delay
26+
)
27+
}
28+
}
29+
30+
stop = () => {
31+
this._clearIntervalIfNecessary()
32+
}
33+
34+
start = delay => {
35+
const _delay = typeof delay === 'number' ? delay : this.props.delay
36+
this._setIntervalIfNecessary(_delay)
37+
}
38+
39+
toggle = () => {
40+
this.intervalId ? this.stop() : this.start()
41+
}
42+
43+
componentDidMount() {
44+
this.start()
45+
}
46+
47+
componentWillReceiveProps(nextProps) {
48+
if (this.props.delay !== nextProps.delay) {
49+
this.stop()
50+
this.start(nextProps.delay)
51+
}
52+
}
53+
54+
componentWillUnmount() {
55+
this.stop()
56+
}
57+
58+
render() {
59+
return renderProps(this.props, {
60+
start: this.start,
61+
stop: this.stop,
62+
toggle: this.toggle,
63+
})
64+
}
65+
}
66+
67+
export default Interval

src/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
export { default as Active } from './components/Active'
2-
export { default as Input } from './components/Input'
32
export { default as Compose } from './components/Compose'
43
export { default as Counter } from './components/Counter'
54
export { default as Focus } from './components/Focus'
65
export { default as FocusManager } from './components/FocusManager'
76
export { default as Form } from './components/Form'
87
export { default as Hover } from './components/Hover'
8+
export { default as Input } from './components/Input'
9+
export { default as Interval } from './components/Interval'
910
export { default as List } from './components/List'
1011
export { default as Map } from './components/Map'
1112
export { default as Set } from './components/Set'

src/index.js.flow

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,18 @@ type StateProps<T> =
212212

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

215+
/* Timer */
216+
217+
type TimerRender = ({|
218+
time: number,
219+
|}) => React.Node
220+
221+
type TimerProps =
222+
| {| delay?: ?number, render: TimerRender |}
223+
| {| delay?: ?number, children: TimerRender |}
224+
225+
declare export class Timer extends React.Component<TimerProps> {}
226+
215227
/* Toggle */
216228

217229
type ToggleChange = ({| on: boolean |}) => void

tests/components/Interval.test.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import * as React from 'react'
2+
import TestRenderer from 'react-test-renderer'
3+
import { last } from './utils'
4+
import { Interval } from '../../src'
5+
6+
jest.useFakeTimers()
7+
8+
test('<Interval />', () => {
9+
const renderFn = jest.fn().mockReturnValue(null)
10+
const renderer = TestRenderer.create(
11+
<Interval delay={500}>{renderFn}</Interval>
12+
)
13+
const lastCalled = () => last(renderFn.mock.calls)[0]
14+
15+
// Initial call
16+
expect(renderFn).toHaveBeenCalledTimes(1)
17+
18+
jest.advanceTimersByTime(1000)
19+
20+
renderer.update(<Interval delay={1000}>{renderFn}</Interval>)
21+
expect(renderFn).toHaveBeenCalledTimes(4)
22+
23+
jest.advanceTimersByTime(2000)
24+
25+
expect(renderFn).toHaveBeenCalledTimes(6)
26+
27+
lastCalled().stop()
28+
jest.advanceTimersByTime(2000)
29+
expect(renderFn).toHaveBeenCalledTimes(6)
30+
31+
lastCalled().start()
32+
jest.advanceTimersByTime(2000)
33+
expect(renderFn).toHaveBeenCalledTimes(8)
34+
35+
lastCalled().toggle()
36+
jest.advanceTimersByTime(2000)
37+
expect(renderFn).toHaveBeenCalledTimes(8)
38+
39+
lastCalled().toggle()
40+
jest.advanceTimersByTime(2000)
41+
expect(renderFn).toHaveBeenCalledTimes(10)
42+
})

0 commit comments

Comments
 (0)