Skip to content

Commit 4882fcd

Browse files
authored
Add composeEvents (#71)
* add composeEvents initial work * add flow types to composeEvents * docs: composeEvents & compose improves * docs: add utils to readme
1 parent 5b1a2b4 commit 4882fcd

File tree

8 files changed

+156
-34
lines changed

8 files changed

+156
-34
lines changed

README.md

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,13 @@ import { Pagination, Tabs, Checkbox } from './MyDumbComponents'
8888
| <h6>OTHER</h6> |
8989
| **\<Compose>** | `{ components }` | _depends on components prop_ | [:point_down:](#composing-components) [:books:](docs/components/Compose.md) |
9090

91+
## Utilities
92+
93+
| Name | |
94+
| ----------------------------- | -------------------------------------- |
95+
| compose(...components) | [:books:](docs/utils/compose.md) |
96+
| composeEvents(...objOfEvents) | [:books:](docs/utils/composeEvents.md) |
97+
9198
## Examples
9299

93100
#### State
@@ -262,23 +269,19 @@ import { Pagination, Tabs, Checkbox } from './MyDumbComponents'
262269
```jsx
263270
<Form initial={{ subject: '', message: '' }}>
264271
{({ input, values }) => (
265-
<form onSubmit={(e) => {
266-
e.preventDefault()
267-
console.log(values)
268-
}}>
269-
<ControlledInput
270-
placeholder="Subject"
271-
{...input('subject').bind}
272-
/>
273-
<ControlledTextArea
274-
placeholder="Message"
275-
{...input('message').bind}
276-
/>
272+
<form
273+
onSubmit={e => {
274+
e.preventDefault()
275+
console.log(values)
276+
}}
277+
>
278+
<ControlledInput placeholder="Subject" {...input('subject').bind} />
279+
<ControlledTextArea placeholder="Message" {...input('message').bind} />
277280
<Submit>Send</Submit>
278281

279282
{/*
280-
input(id) => { bind, set, value }
281-
*/}
283+
input(id) => { bind, set, value }
284+
*/}
282285
</form>
283286
)}
284287
</Form>

docs/components/Compose.md

Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -39,22 +39,10 @@ Also, you can use a built-in Compose component and pass components on `component
3939
</Compose>
4040
```
4141

42-
In addition you can use a utility to create a Composed component
42+
In addition you can use `compose()` utility to create a Composed component.
43+
[See docs](/docs/utils/compose.md)
4344

44-
```js
45-
import { compose } from 'react-powerplug' // note lowercased (c)ompose
46-
```
47-
48-
```jsx
49-
const ToggleCounter = compose(
50-
<Counter initial={5} />,
51-
<Toggle initial={false} />
52-
)
53-
54-
<ToggleCounter>
55-
{(counter, toggle) => (...)}
56-
</ToggleCounter>
57-
```
45+
---
5846

5947
Behind the scenes, that's what happens:
6048

@@ -77,16 +65,11 @@ Behind the scenes, that's what happens:
7765
</Counter>
7866
```
7967

80-
Because of that, when you use `toggle` function, only `<Toggle>` will be rerendered, but if you use `inc` or `dec` functions, both `<Counter>` and `<Toggle>` will be rerendered. **Even** using `compose()` utility.
81-
8268
## Compose Props
8369

8470
**components** _(required)_
8571
Set an array of React PowerPlug components (instance elements, not types) to compose.
8672

87-
Note: if you use `compose` utility to create Composed components, you don't
88-
need to pass an array, just pass by arguments: `compose(<Foo />, <Bar />, <Baz />)`
89-
9073
## Compose Children Props
9174

9275
The render props function provided will receive n arguments, each of them being

docs/utils/compose.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# compose
2+
3+
The compose utility is a factory version of [Compose](/docs/components/Compose.md) component.
4+
5+
```js
6+
import { compose, Counter, Toggle } from 'react-powerplug' // note lowercased (c)ompose
7+
8+
// the order matters
9+
const ToggleCounter = compose(
10+
<Counter initial={5} />, // accept a element
11+
Toggle, // or just a component
12+
)
13+
14+
<ToggleCounter>
15+
{(counter, toggle) => {
16+
// counter.inc, counter.dec, counter.count
17+
// toggle.on, toggle.toggle, etc.
18+
}}
19+
</ToggleCounter>
20+
```
21+
22+
---
23+
24+
Behind the scenes, that's what happens:
25+
26+
```jsx
27+
<Counter initial={5}>
28+
{counter => (
29+
<Toggle>
30+
{toggle => (
31+
/* ... */
32+
)}
33+
</Toggle>
34+
)}
35+
</Counter>
36+
```

docs/utils/composeEvents.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# composeEvents
2+
3+
The composeEvents utility helps you when you need to pass the same callback more than once.
4+
5+
```jsx
6+
import { Hover, composeEvents } from 'react-powerplug'
7+
8+
const HoveredDiv = ({ children, onMouseEnter, onMouseLeave, ...restProps }) => (
9+
<Hover>
10+
{({ isHover, bindHover }) => (
11+
<div
12+
{...composeEvents({ onMouseEnter, onMouseLeave }, bindHover)}
13+
{...restProps}
14+
children={children(isHover)}
15+
/>
16+
)}
17+
</Hover>
18+
)
19+
```
20+
21+
It's just merge array of events object into single one.
22+
23+
```jsx
24+
const callbacks = composeEvents(
25+
{
26+
onMouseEnter: event => console.log('first call', event),
27+
onMouseLeave: event => console.log('first call', event),
28+
},
29+
{
30+
onMouseEnter: event => console.log('second call', event),
31+
}
32+
)
33+
34+
/**
35+
* callbacks = {
36+
* onMouseEnter: Function,
37+
* onMouseLeave: Function
38+
* }
39+
*/
40+
41+
const App = () => (
42+
<>
43+
<div {...callbacks} />
44+
<div onMouseEnter={callbacks.onMouseEnter} />
45+
</>
46+
)
47+
```

src/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,5 @@ export { default as Touch } from './components/Touch'
1515
export { default as Value } from './components/Value'
1616

1717
export { default as compose } from './utils/compose'
18+
export { default as composeEvents } from './utils/composeEvents'
1819
export { default as renderProps } from './utils/renderProps'

src/index.js.flow

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,3 +255,9 @@ type ValueProps<T> =
255255
| {| initial: T, onChange?: ValueChange<T>, children: ValueRender<T> |}
256256

257257
declare export class Value<T> extends React.Component<ValueProps<T>> {}
258+
259+
/* composeEvents */
260+
261+
type Events = { [name: string]: Function }
262+
263+
declare export function composeEvents(...Array<Events>): Events

src/utils/composeEvents.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
const composeEvents = (...objEvents) => {
2+
return objEvents.reverse().reduce((allEvents, events) => {
3+
let append = {}
4+
5+
for (const key in events) {
6+
append[key] = allEvents[key]
7+
? // Already have this event: let's merge
8+
(...args) => {
9+
events[key](...args)
10+
allEvents[key](...args)
11+
}
12+
: // Don't have this event yet: just assign the event
13+
events[key]
14+
}
15+
16+
return { ...allEvents, ...append }
17+
})
18+
}
19+
20+
export default composeEvents

tests/utils/composeEvents.test.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { composeEvents } from '../../src'
2+
3+
test('composeEvents should call all events', () => {
4+
const arr = []
5+
const composed = composeEvents(
6+
{
7+
onMouseEnter: () => arr.push(1),
8+
},
9+
{
10+
onMouseEnter: () => arr.push(2),
11+
onMouseLeave: () => arr.push(3),
12+
},
13+
{
14+
onMouseEnter: () => arr.push(4),
15+
onMouseLeave: () => arr.push(5),
16+
},
17+
{
18+
onMouseLeave: () => arr.push(6),
19+
}
20+
)
21+
22+
composed.onMouseEnter()
23+
composed.onMouseLeave()
24+
25+
expect(arr).toEqual([1, 2, 4, 3, 5, 6])
26+
})

0 commit comments

Comments
 (0)