├── .babelrc
├── .editorconfig
├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── banner.png
├── package.json
├── react-dream-logo.png
├── react-dream-logo.svg
├── src
├── ReactDream.js
├── createElementWithProps.js
├── index.js
├── internals
│ ├── doAp.js
│ ├── doConcat.js
│ ├── doContramap.js
│ ├── doMap.js
│ ├── doPromap.js
│ ├── doRotate.js
│ ├── doScale.js
│ └── doTranslate.js
├── isReferentiallyTransparentFunctionComponent.js
├── partialApplication
│ ├── addProps.js
│ ├── ap.js
│ ├── chain.js
│ ├── concat.js
│ ├── contramap.js
│ ├── debug.js
│ ├── defaultProps.js
│ ├── fork.js
│ ├── log.js
│ ├── map.js
│ ├── name.js
│ ├── promap.js
│ ├── propTypes.js
│ ├── removeProps.js
│ ├── rotate.js
│ ├── scale.js
│ ├── style.js
│ └── translate.js
├── styleFromProps.js
└── withStyleFromProps.js
├── test
├── ReactDream.js
├── createElementWithProps.js
├── dsl.js
├── index.js
├── internals
│ ├── doAp.js
│ ├── doConcat.js
│ ├── doContramap.js
│ ├── doMap.js
│ ├── doPromap.js
│ └── index.js
├── partialApplication.js
└── styleFromProps.js
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "es2015",
4 | "react",
5 | "stage-3"
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | end_of_line = lf
5 | insert_final_newline = true
6 | trim_trailing_whitespace = true
7 | indent_style = space
8 | indent_size = 2
9 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .sagui
3 | coverage
4 | dist
5 | node_modules
6 | npm-debug.log
7 | yarn-error.log
8 | .next
9 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - '7'
4 | before_script:
5 | - npm install
6 | script:
7 | - npm test
8 | deploy:
9 | skip_cleanup: true
10 | provider: npm
11 | email: fernando.via@gmail.com
12 | api_key:
13 | secure: Tbh+YgTCcdhInbJKuLwGGJLH2wUzDNNgNeBG618OG7Hfk5VO9O/ehuYeZ9KM6oQiVGXHC1w1mDCGaVcegYV/gBAliqktIrvfYi03Xv7KG9J6Wap3xwiF+Ji6doI/dFeEAC/2Z3cgEq//XbpssSjCuSC/YyppI4TvKPjNMRcYp2wOUM4M/bkkgBWe1bALFliQluB4GGzJ8YGmoENCvdUas2XRbbf1dbZTkuwHES2doYJMqGeG92m/6PS+KpkCLh/sf2CA/hOsLIURwH3KenhevpuoSsz7ZCLLZnqbgE6sWaVz6E5k7g+LOZ1wHIC8UIF49KfEZMZr4HGBuapT7NQqpCHQpFA9AevGnkLtavqs4fmrHfDehKROwfPU/4i3tAtuB1nu3MMnHkWj7temrLyyEGfcJcseUjGGngscbWnmt/xG08+atcl4krda9ZteyXRwrqI6dTrm+XmUvKzyVmVwGP+g0iIKAjfgA149EZSo1i9r1B9MhNrsqILycN3bzBbBaMwkLGNgVM9T1rU7yL/IUN+6W2A78yw09pfTglK3O35AZoR+GPLFnpQOhRsGnYZYfQE7A+Ch6iAAytgTaM3ZYtjys0KDcRXQa861WVY6SaNpXRISBfUR7++Azc1QnnHg5/xQx8kuWukRfJVOlrOTKQgK+qeKd1fZwtnXouaHXxE=
14 | on:
15 | tags: true
16 | repo: xaviervia/react-dream
17 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Fernando Via Canel
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # React Dream
4 |
5 | [](https://travis-ci.org/xaviervia/react-dream)
6 | [](https://www.npmjs.com/package/react-dream)
7 |
8 | [Fantasy Land](https://github.com/fantasyland/fantasy-land) type for [React Components](https://facebook.github.io/react/)
9 |
10 | **Caution: Experimental** (not _extremely_ anymore though)
11 |
12 | ## Installation
13 |
14 | ```
15 | npm add react-dream
16 | ```
17 |
18 | You will also need a couple of peer dependencies:
19 |
20 | ```
21 | npm add react recompose
22 | ```
23 |
24 | ## Table of contents
25 |
26 | - [Usage](#usage)
27 | - [API](#api)
28 | - [map](#mapcomponent--enhancedcomponent)
29 | - [contramap](#contramapprops--modifiedprops)
30 | - [promap](#promapprops--modifiedprops-component--enhancedcomponent)
31 | - [ap + of](#ap--of)
32 | - [concat](#concat)
33 | - [chain](#chain)
34 | - [fork](#forkcomponent--)
35 | - [addProps](#addpropsprops--propstoadd--object)
36 | - [removeProps](#removepropspropnamestoremove--string)
37 | - [defaultProps](#defaultpropsprops--object)
38 | - [propTypes](#proptypesproptypes--object)
39 | - [style](#styleprops--stylestoadd--object)
40 | - [name](#namenewdisplayname--string)
41 | - [rotate](#rotateprops--rotation--number)
42 | - [scale](#scaleprops--scalefactor--number)
43 | - [translate](#translateprops--x--number-y--number-z--number)
44 | - [log](#logprops--value--any)
45 | - [debug](#debug)
46 | - [Built-in primitives](#built-in-primitives)
47 |
48 | ## Usage
49 |
50 | ### Lifting React components into ReactDream
51 |
52 | For example, for a ReactNative View:
53 |
54 | ```js
55 | import ReactDream from 'react-dream'
56 | import { View } from 'react-native'
57 |
58 | const DreamView = ReactDream(View)
59 | ```
60 |
61 | …or for a web `div`:
62 |
63 | ```js
64 | import React from 'react'
65 | import ReactDream from 'react-dream'
66 |
67 | const DreamView = ReactDream(props =>
)
68 | ```
69 |
70 | ### Complete example
71 |
72 | Here is an extensive example that can be found in [examples](https://github.com/xaviervia/react-dream-examples/blob/master/pages/index.js):
73 |
74 | > If you are not familiar with Fantasy Land types, I can highly recommend the [video tutorials by Brian Lonsdorf](https://egghead.io/instructors/brian-lonsdorf)
75 |
76 | > Note that this and the following examples use already-built wrappers that you can pull from [react-dream-web-builtins](https://github.com/xaviervia/react-dream-web-builtins). This are convenient but might not be easy to tree shake when bundling, so use with caution.
77 |
78 | ```js
79 | import React from 'react'
80 | import { render } from 'react-dom'
81 | import { withHandlers, withState } from 'recompose'
82 | import { of } from 'react-dream'
83 | import { Html } from 'react-dream-web-builtins'
84 |
85 | const withChildren = North => South => Wrapper => ({ north, south, wrapper, ...props }) =>
86 |
87 |
88 |
89 |
90 |
91 | const Title = Html.H1
92 | .style(() => ({
93 | fontFamily: 'sans-serif',
94 | fontSize: 18,
95 | }))
96 | .name('Title')
97 |
98 | const Tagline = Html.P
99 | .style(() => ({
100 | fontFamily: 'sans-serif',
101 | fontSize: 13,
102 | }))
103 | .name('Tagline')
104 |
105 | const HeaderWrapper = Html.Header
106 | .removeProps('clicked', 'updateClicked')
107 | .style(({ clicked }) => ({
108 | backgroundColor: clicked ? 'red' : 'green',
109 | cursor: 'pointer',
110 | padding: 15,
111 | }))
112 | .name('HeaderWrapper')
113 | .map(
114 | withHandlers({
115 | onClick: ({ clicked, updateClicked }) => () => updateClicked(!clicked),
116 | })
117 | )
118 | .map(withState('clicked', 'updateClicked', false))
119 |
120 | const Header = of(withChildren)
121 | .ap(Title)
122 | .ap(Tagline)
123 | .ap(HeaderWrapper)
124 | .contramap(({ title, tagline }) => ({
125 | north: { children: title },
126 | south: { children: tagline },
127 | }))
128 | .name('Header')
129 |
130 | Header.fork(Component =>
131 | render(
132 | ,
136 | document.getElementById('root')
137 | )
138 | )
139 | ```
140 |
141 | Render part could also be written:
142 |
143 | ```js
144 | render(
145 | ,
149 | document.getElementById('root')
150 | )
151 | ```
152 |
153 | ### Pointfree style
154 |
155 | All methods of `ReactDream` are available as functions that can be partially applied and then take the ReactDream component as the last argument. This makes it possible to write compositions that can then be applied to a ReactDream object. The elements of the example above could be rewritten as:
156 |
157 | ```js
158 | import React from 'react'
159 | import { render } from 'react-dom'
160 | import { compose, withHandlers, withState } from 'recompose'
161 | import { ap, removeProps, contramap, map, name, of, style } from 'react-dream'
162 | import { Html } from 'react-dream-web-builtins'
163 |
164 | const withChildren = North => South => Wrapper => ({ north, south, wrapper, ...props }) =>
165 |
166 |
167 |
168 |
169 |
170 | const Title = compose(
171 | name('Title'),
172 | style(() => ({
173 | fontFamily: 'sans-serif',
174 | fontSize: 18,
175 | }))
176 | )(Html.H1)
177 |
178 | const Tagline = compose(
179 | name('Tagline'),
180 | style(() => ({
181 | fontFamily: 'sans-serif',
182 | fontSize: 13,
183 | }))
184 | )(Html.P)
185 |
186 | const HeaderWrapper = compose(
187 | map(withState('clicked', 'updateClicked', false)),
188 | map(
189 | withHandlers({
190 | onClick: ({ clicked, updateClicked }) => () => updateClicked(!clicked),
191 | })
192 | ),
193 | name('HeaderWrapper'),
194 | style(({ clicked }) => ({
195 | backgroundColor: clicked ? 'red' : 'green',
196 | cursor: 'pointer',
197 | padding: 15,
198 | })),
199 | removeProps('clicked', 'updateClicked')
200 | )(Html.Header)
201 |
202 | const Header = compose(
203 | name('Header'),
204 | contramap(({ title, tagline }) => ({
205 | north: { children: title },
206 | south: { children: tagline },
207 | })),
208 | ap(HeaderWrapper),
209 | ap(Tagline),
210 | ap(Title)
211 | )(of(withChildren))
212 | ```
213 |
214 | ## API
215 |
216 | The following are the methods of objects of the ReactDream type. There are two types of methods:
217 |
218 | - Algebras: they come from Fantasy Land, and they are defined following that specification.
219 | - Helpers: they are derivations (use cases) of the methods that come from the algebras. Added for convenience.
220 |
221 | ReactDream implements these Fantasy Land algebras:
222 |
223 | - Profunctor (map, contramap, promap)
224 | - Applicative (of, ap)
225 | - Semigroup (concat)
226 | - Monad (chain)
227 |
228 | Check [Fantasy Land](https://github.com/fantasyland/fantasy-land) for more details.
229 |
230 | ### map(Component => EnhancedComponent)
231 |
232 | `map` allows to wrap the function with regular higher-order components, such as the ones provided by [recompose](https://github.com/acdlite/recompose).
233 |
234 | ```js
235 | import React from 'react'
236 | import ReactDream from 'react-dream'
237 | import { withHandlers, withState } from 'recompose'
238 |
239 | const Counter = ReactDream(({counter, onClick}) =>
240 |
241 |
242 |
{counter}
243 |
244 | )
245 | .map(
246 | withHandlers({
247 | onClick: ({ counter, updateCount }) => () => updateCount(counter + 1),
248 | })
249 | )
250 | .map(withState('counter', 'updateCount', 0))
251 | ```
252 |
253 | This is because `map` expects a function from `a -> b` in the general case but from `Component -> a` in this particular case since holding components is the intended usage of ReactDream. Higher-order components are functions from `Component -> Component`, so they perfectly fit the bill.
254 |
255 | ### contramap(props => modifiedProps)
256 |
257 | `contramap` allows to preprocess props before they reach the component.
258 |
259 | ```js
260 | const Title = H1
261 | .contramap(({label}) => ({
262 | children: label
263 | }))
264 | .name('Title')
265 |
266 | render(
267 | ,
270 | domElement
271 | )
272 | ```
273 |
274 | This is a common pattern for higher-order Components, and the key advantage of using `contramap` instead of `map` for this purpose is that if the wrapped component is a stateless, function component, you avoid an unnecessary call to React. Another advantage is that functions passed to `contramap` as an argument are simply pure functions, without mentioning React at all, with the signature `Props -> Props`.
275 |
276 | ### promap(props => modifiedProps, Component => EnhancedComponent)
277 |
278 | `promap` can be thought of as a shorthand for doing `contramap` and `map` at the same time. The first argument to it is the function that is going to be used to `contramap` and the second is the one to be used to `map`:
279 |
280 | ```js
281 | const Header = Html.Div
282 | .promap(
283 | ({title}) => ({children: title}),
284 | setDisplayName('Header')
285 | )
286 | ```
287 |
288 | ### ap + of
289 |
290 | `ap` allows you to apply a higher-order components to regular components, and `of` allows you to lift any value to `ReactDream`, which is useful for lifting higher-order components.
291 |
292 | Applying second-order components (`Component -> Component`) can also be done with `map`: where `ap` shines is in allowing you to apply a higher-order component that takes two or more components (third or higher order, such as `Component -> Component -> Component -> Component`), that is otherwise not possible with `map`. This makes it possible to abstract control flow or composition patterns in higher-order components:
293 |
294 | **Control flow example**
295 |
296 | ```js
297 | const eitherLeftOrRight = Left => Right => ({left, ...props}) =>
298 | left
299 | ?
300 | :
301 |
302 | const TitleOrSubtitle = of(eitherLeftOrRight)
303 | .ap(Html.H1)
304 | .ap(Html.H2)
305 | .addProps({isTitle} => ({
306 | left: isTitle
307 | }))
308 |
309 | render(
310 |
311 | This will be an H1 title
312 |
313 | , domElement
314 | )
315 | ```
316 |
317 | **Parent-children pattern example**
318 |
319 | ```js
320 | const withChildren = North => South => Wrapper => ({north, south, wrapper, ...props}) =>
321 |
322 |
323 |
324 |
325 |
326 | const PageHeader = of(withChildren)
327 | .ap(Html.H1)
328 | .ap(Html.P)
329 | .ap(Html.Header)
330 | .addProps({title, subtitle} => ({
331 | north: { children: title },
332 | south: { children: subtitle },
333 | }))
334 |
335 | render(
336 |
340 | , domElement
341 | )
342 | ```
343 |
344 | ### concat
345 |
346 | > Requires React 16+
347 |
348 | `concat` constructs a new component that wraps the current component and another one being passed as siblings, passing the props to both of them. For example:
349 |
350 | ```js
351 | import { Html } from 'react-dream'
352 |
353 | const Header = Html.H1
354 | .concat(Html.P)
355 | ```
356 |
357 | Since props are passed to both elements in the composition, invoking the above defined `Header` like this:
358 |
359 | ```js
360 | Hello
361 | ```
362 |
363 | …will result in:
364 |
365 | ```html
366 |
Hello
367 |
Hello
368 | ```
369 |
370 | So to make concatenation more useful, it is necessary for the elements to be configured to capture the props that are useful for them:
371 |
372 | ```js
373 | import { Html } from 'react-dream'
374 |
375 | const Header = Html.H1
376 | .contramap(({title}) => ({children: title}))
377 | .concat(
378 | Html.P
379 | .contramap(({description}) => ({children: description}))
380 | )
381 | ```
382 |
383 | This way the composition can be used like this:
384 |
385 | ```js
386 |
390 | ```
391 |
392 | …and will result in:
393 |
394 | ```html
395 |
Hello
396 |
World!
397 | ```
398 |
399 | Note: while `concat` is for all practical purposes associative (as far as the resulting elements in the DOM are concerned), the React Components themselves are not joined together in an associative way, and this can be seen in the React DevTools. This violation of associativity is what makes it impossible for ReactDream to implement Monoid.
400 |
401 | ### chain
402 |
403 | `chain` is useful as a escape hatch if you want to escape from ReactDream and do something very React-y
404 |
405 | ```js
406 | import ReactDream from 'react-dream'
407 | import { Svg } from 'react-dream-web-builtins'
408 |
409 | const wrapWithGLayer = Component => ReactDream(props =>
410 |
411 |
412 |
413 | )
414 |
415 | const LayerWithCircle = Svg.Circle
416 | .contramap(() => ({
417 | r: 5,
418 | x: 10,
419 | y: 10
420 | })
421 | .chain(wrapWithGLayer)
422 | ```
423 |
424 | Aside from Fantasy Land algebras, ReactDream provides the methods:
425 |
426 | ### fork(Component => {})
427 |
428 | Calls the argument function with the actual component in the inside. This function is intended to be used to get the component for rendering, which is a side effect:
429 |
430 | ```js
431 | H1.fork(Component => render(Hello, domElement))
432 | ```
433 |
434 | ### addProps(props => propsToAdd : Object)
435 |
436 | `addProps` allows you to pass a function whose result will be merged with the regular props. This is useful to add derived props to a component:
437 |
438 | ```js
439 | import { Svg } from 'react-dream-web-builtins'
440 |
441 | const Picture = Svg.Svg
442 | .addProps(props => ({
443 | viewBox: `0 0 ${props.width} ${props.height}`
444 | }))
445 |
446 | render(
447 | ,
451 | domElement
452 | )
453 | ```
454 |
455 | The new props will be merged below the regular ones, so that the consumer can always override your props:
456 |
457 | ```diff
458 | import { Svg } from 'react-dream-web-builtins'
459 |
460 | const Picture = Svg.Svg
461 | .addProps(props => ({
462 | + // This will be now ignored
463 | viewBox: `0 0 ${props.width} ${props.height}`
464 | }))
465 |
466 | render(
467 | ,
472 | domElement
473 | )
474 | ```
475 |
476 | #### `addProps` is a use case of `contramap`
477 |
478 | ```js
479 | .addProps(({width, height}) => ({
480 | viewBox: `0 0 ${props.width} ${props.height}`
481 | }))
482 | ```
483 |
484 | …is equivalent to:
485 |
486 | ```js
487 | .contramap(props => ({
488 | ...props,
489 | viewBox: `0 0 ${props.width} ${props.height}`
490 | }))
491 | ```
492 |
493 | ### removeProps(...propNamesToRemove : [String])
494 |
495 | `removeProps` filters out props. Very useful to avoid the React warnings of unrecognized props.
496 |
497 | ```js
498 | const ButtonWithStates = Html.Button
499 | .removeProps('hovered', 'pressed')
500 | .style(({hovered, pressed}) => ({
501 | color: pressed ? 'red' : (hovered ? 'orange' : 'black')
502 | }))
503 | ```
504 |
505 | #### `removeProps` is an use case of `contramap`
506 |
507 | ```js
508 | .removeProps('title', 'hovered')
509 | ```
510 |
511 | …is equivalent to:
512 |
513 | ```js
514 | .contramap(({title, hovered, ...otherProps}) => otherProps)
515 | ```
516 |
517 | ### defaultProps(props : Object)
518 |
519 | `defaultProps` allows you to set the, well, `defaultProps` of the wrapped React component.
520 |
521 | ```js
522 | const SubmitButton = Html.Button
523 | .defaultProps({ type: 'submit' })
524 | ```
525 |
526 | #### `defaultProps` is an use case of `map`
527 |
528 | ```js
529 | const SubmitButton = Html.Button
530 | .defaultProps({ type: 'submit' })
531 | ```
532 |
533 | Under the hood is using `recompose`’s `defaultProps` function:
534 |
535 | ```js
536 | import { defaultProps } from 'recompose'
537 |
538 | const SubmitButton = Html.Button
539 | .map(defaultProps({ type: 'submit' }))
540 | ```
541 |
542 | ### propTypes(propTypes : Object)
543 |
544 | `propTypes` sets the `propTypes` of the React component.
545 |
546 | ```js
547 | import PropTypes from 'prop-types'
548 |
549 | const Title = Html.H1
550 | .style(({ highlighted }) => ({
551 | backgroundColor: highlighted ? 'yellow' : 'transparent'
552 | }))
553 | .propTypes({
554 | children: PropTypes.node,
555 | highlighted: PropTypes.bool
556 | })
557 | ```
558 |
559 | #### `propTypes` is an use case of `map`
560 |
561 | The example above is equivalent to:
562 |
563 | ```js
564 | import PropTypes from 'prop-types'
565 | import { setPropTypes } from 'recompose'
566 |
567 | const Title = Html.H1
568 | .style(({ highlighted }) => ({
569 | backgroundColor: highlighted ? 'yellow' : 'transparent'
570 | }))
571 | .map(setPropTypes({
572 | children: PropTypes.node,
573 | highlighted: PropTypes.bool
574 | }))
575 | ```
576 |
577 | ### style(props => stylesToAdd : Object)
578 |
579 | The `style` helper gives a simple way of adding properties to the `style` prop of the target component. It takes a function from props to a style object. The function will be invoked each time with the props. The result will be set as the `style` prop of the wrapper component. If there are styles coming from outside, they will be merged together with the result of this function. For example:
580 |
581 | ```js
582 | const Title = Html.H1
583 | .style(props => ({color: highlighted ? 'red' : 'black'}))
584 |
585 | render(
586 | ,
590 | domElement
591 | )
592 | ```
593 |
594 | The resulting style will be: `{ color: 'red', backgroundColor: 'green' }`.
595 |
596 | #### `style` is an use case of `contramap`
597 |
598 | ```js
599 | .style(({hovered}) => ({
600 | color: hovered ? 'red' : 'black'
601 | }))
602 | ```
603 |
604 | …is equivalent to:
605 |
606 | ```js
607 | .contramap(props => ({
608 | style: {
609 | color: props.hovered ? 'red' : 'black',
610 | ...props.style
611 | },
612 | ...props
613 | }))
614 | ```
615 |
616 | ### name(newDisplayName : String)
617 |
618 | Sets the `displayName` of the component:
619 |
620 | ```js
621 | const Tagline = H2.name('Tagline')
622 | ```
623 |
624 | #### `name` is an use case of `map`
625 |
626 | ```js
627 | .name('Tagline')
628 | ```
629 |
630 | …is equivalent to:
631 |
632 | ```js
633 | import { setDisplayName } from 'recompose'
634 |
635 | .map(setDisplayName('Title'))
636 | ```
637 |
638 | ### rotate(props => rotation : number)
639 |
640 | `rotate` sets up a style `transform` property with the specified rotation, in degrees. If there is a transform already, `rotate` will append to it:
641 |
642 | ```js
643 | const Title = Html.H1
644 | .rotate(props => 45)
645 |
646 | render(
647 | ,
648 | document.getElementById('root')
649 | )
650 | ```
651 |
652 | …will result in `transform: 'translateX(20px) rotate(45deg)'`
653 |
654 | > Just a reminder: rotations start from the top left edge as the axis, which is rarely what one wants. If you want the rotation to happen from the center, you can set `transform-origin: 'center'`, that with ReactDream would be `.style(props => ({transformOrigin: 'center'}))`.
655 |
656 | #### `rotate` is an use case of `contramap`
657 |
658 | ```js
659 | .rotate(props => 45)
660 | ```
661 |
662 | …is equivalent to:
663 |
664 | ```js
665 | .contramap(props => ({
666 | style: {
667 | transform: props.transform
668 | ? `${props.transform} rotate(45deg)`
669 | : 'rotate(45deg)'
670 | ...props.style
671 | },
672 | ...props
673 | }))
674 | ```
675 |
676 | ### scale(props => scaleFactor : number)
677 |
678 | `scale` sets up a style `transform` property with the specified scaling factor. If there is a transform already, `scale` will append to it:
679 |
680 | ```js
681 | const Title = Html.H1
682 | .scale(props => 1.5)
683 |
684 | render(
685 | ,
686 | document.getElementById('root')
687 | )
688 | ```
689 |
690 | …will result in `transform: 'translateX(20px) scale(1.5)'`
691 |
692 | ##### `scale` is an use case of `contramap`
693 |
694 | ```js
695 | .scale(props => 2)
696 | ```
697 |
698 | …is equivalent to:
699 |
700 | ```js
701 | .contramap(props => ({
702 | style: {
703 | transform: props.transform
704 | ? `${props.transform} scale(2)`
705 | : 'scale(2)'
706 | ...props.style
707 | },
708 | ...props
709 | }))
710 | ```
711 |
712 | ### translate(props => [x : number, y : number, z : number])
713 |
714 | `translate` allows you to easily set up the `transform` style property with the specified displacement. If there is a transform already, `translate` will append to it:
715 |
716 | ```js
717 | const Title = Html.H1
718 | .translate(props => [30])
719 | .translate(props => [null, 30])
720 | .translate(props => [null, null, 30])
721 | ```
722 |
723 | …will result in `transform: 'translateZ(30px) translateY(30px) translateX(30px)'`
724 |
725 | #### `translate` is an use case of `contramap`
726 |
727 | ```js
728 | .translate(({x, y}) => [x, y])
729 | ```
730 |
731 | …is equivalent to:
732 |
733 | ```js
734 | .contramap(props => ({
735 | style: {
736 | transform: props.transform
737 | ? `${props.transform} translate(${x}px, ${y}px)`
738 | : `translate(${x}px, ${y}px)`
739 | ...props.style
740 | },
741 | ...props
742 | }))
743 | ```
744 |
745 | ## Debugging
746 |
747 | The downside of chaining method calls is that debugging is not super intuitive. Since there are no statements, it’s not possible to place a `console.log()` or `debugger` call in the middle of the chain without some overhead. To simplify that, two methods for debugging are bundled:
748 |
749 | ### log(props => value : any)
750 |
751 | Whenever the Component is called with new props, it will print:
752 |
753 | - The component displayName
754 | - The value by the argument function. The value can be anything, it will be passed as-is to the `console.log` function.
755 |
756 | Pretty useful to debug what exactly is happening in the chain:
757 |
758 | ```js
759 | const Title = Html.H1
760 | .log(props => 'what props gets to the H1?')
761 | .log(props => props)
762 | .contramap(({hovered, label}) => ({
763 | children: hovered ? 'Hovered!' : label
764 | }))
765 | .log(({label}) => 'is there a label before the contramap? ' + label)
766 | .name('Title')
767 | .log(({label}) => 'does it also get a label from outside? ' + label)
768 |
769 | render(
770 | ,
771 | domElement
772 | )
773 | ```
774 |
775 | `log` will become a no-op when the `NODE_ENV` is `production`.
776 |
777 | For more details check out [@hocs/with-log](https://github.com/deepsweet/hocs/tree/master/packages/with-log) documentation which React Dream is using under the hood.
778 |
779 | #### `log` is an use case of `map`
780 |
781 | ```js
782 | .log(({a}) => `a is: ${a}`)
783 | ```
784 |
785 | …is equivalent to:
786 |
787 | ```js
788 | import withLog from '@hocs/with-log'
789 |
790 | .map(withLog(({a}) => `a is: ${a}`))
791 | ```
792 |
793 | ### debug()
794 |
795 | **Careful**: This method allows you to inject a `debugger` statement at that point in the chain. The result will allow you to inspect the Component and its props, from the JavaScript scope of the [@hocs/with-debugger higher-order component](https://github.com/deepsweet/hocs/tree/master/packages/with-debugger).
796 |
797 | ```js
798 | import React from 'react'
799 | import { render } from 'react-dom'
800 | import { Html } from 'react-dream-web-builtins'
801 |
802 | const App = Html.Div
803 | .debug()
804 | .removeProps('a', 'c', 'randomProp')
805 | .addProps(() => ({
806 | a: '1',
807 | c: '4'
808 | }))
809 | ```
810 |
811 | It will be called on each render of the component.
812 |
813 | `debug` will become a no-op when the `NODE_ENV` is `production`.
814 |
815 | For more details check out [@hocs/with-debugger](https://github.com/deepsweet/hocs/tree/master/packages/with-debugger) documentation which React Dream is using under the hood.
816 |
817 | #### `debug` is an use case of `map`
818 |
819 | ```js
820 | .debug()
821 | ```
822 |
823 | …is equivalent to:
824 |
825 | ```js
826 | import withDebugger from '@hocs/with-debugger'
827 |
828 | .map(withDebugger)
829 | ```
830 |
831 | ## Built-in Primitives
832 |
833 | A separate package, [react-dream-web-builtins](https://github.com/xaviervia/react-dream-web-builtins) ships with a complete set of HTML and SVG primitives lifted into the type. You can access them like:
834 |
835 | ```js
836 | import { Svg, Html } from 'react-dream-web-builtins'
837 |
838 | const MyDiv = Html.Div
839 |
840 | const MyLayer = Svg.G
841 | ```
842 |
843 | Read more in the package [README]((https://github.com/xaviervia/react-dream-web-builtins))
844 |
845 | ## License
846 |
847 | [MIT](LICENSE)
848 |
--------------------------------------------------------------------------------
/banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xaviervia/react-dream/cc6fcd80dbf40a39824eaa25b7f1d6befc05335e/banner.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-dream",
3 | "version": "0.5.1",
4 | "description": "Fantasy Land type for React Components",
5 | "main": "dist/index.js",
6 | "files": [
7 | "dist"
8 | ],
9 | "scripts": {
10 | "dist": "babel src/ -d dist/ --ignore spec.js",
11 | "prepublishOnly": "npm run dist",
12 | "test": "babel-node test"
13 | },
14 | "repository": {
15 | "type": "git",
16 | "url": "git+https://github.com/xaviervia/react-dream.git"
17 | },
18 | "keywords": [
19 | "react",
20 | "fantasy-land",
21 | "fantasyland",
22 | "functional",
23 | "functional-programming",
24 | "fp"
25 | ],
26 | "author": "Fernando Via Canel ",
27 | "license": "MIT",
28 | "bugs": {
29 | "url": "https://github.com/xaviervia/react-dream/issues"
30 | },
31 | "homepage": "https://github.com/xaviervia/react-dream#readme",
32 | "devDependencies": {
33 | "@klarna/higher-order-components": "^3.0.1",
34 | "babel-cli": "^6.24.1",
35 | "babel-preset-es2015": "^6.24.1",
36 | "babel-preset-react": "^6.24.1",
37 | "babel-preset-stage-3": "^6.24.1",
38 | "jsdom": "^11.12.0",
39 | "jsdom-global": "^3.0.2",
40 | "prettier": "^1.5.3",
41 | "ramda": "^0.25.0",
42 | "react": "^16.0.0",
43 | "react-test-renderer": "^16.0.0",
44 | "recompose": "^0.24.0",
45 | "washington": "^2.0.0-rc.3"
46 | },
47 | "peerDependencies": {
48 | "react": "^16.0.0",
49 | "recompose": "^0.24.0"
50 | },
51 | "dependencies": {
52 | "@hocs/with-debugger": "^0.1.0",
53 | "@hocs/with-log": "^0.1.0"
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/react-dream-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xaviervia/react-dream/cc6fcd80dbf40a39824eaa25b7f1d6befc05335e/react-dream-logo.png
--------------------------------------------------------------------------------
/react-dream-logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
26 |
--------------------------------------------------------------------------------
/src/ReactDream.js:
--------------------------------------------------------------------------------
1 | import compose from 'recompose/compose'
2 | import setDisplayName from 'recompose/setDisplayName'
3 | import recomposeDefaultProps from 'recompose/defaultProps'
4 | import setPropTypes from 'recompose/setPropTypes'
5 | import withDebugger from '@hocs/with-debugger'
6 | import withLog from '@hocs/with-log'
7 | import doAp from './internals/doAp'
8 | import doConcat from './internals/doConcat'
9 | import doContramap from './internals/doContramap'
10 | import doMap from './internals/doMap'
11 | import doPromap from './internals/doPromap'
12 | import doRotate from './internals/doRotate'
13 | import doTranslate from './internals/doTranslate'
14 | import doScale from './internals/doScale'
15 | import styleFromProps from './styleFromProps'
16 |
17 | // ALGEBRAS
18 | // ////////////////////////////////////////////////////////////////////////// //
19 |
20 | // ap : higherOrderComponent -> ReactDream -> ReactDream
21 | const ap = higherOrderComponent => ReactDreamComponent =>
22 | ReactDream(doAp(higherOrderComponent)(ReactDreamComponent))
23 |
24 | // chain : Component -> (Component -> ReactDream) -> ReactDream
25 | const chain = Component => kleisliReactDreamComponent => kleisliReactDreamComponent(Component)
26 |
27 | // map : Component -> (Component -> Component) -> ReactDream
28 | const map = Component => higherOrderComponent => ReactDream(doMap(higherOrderComponent)(Component))
29 |
30 | // concat : Component -> Component -> ReactDream
31 | const concat = Component => OtherComponent =>
32 | ReactDream(doConcat(OtherComponent.Component)(Component))
33 |
34 | // contramap : Component -> (a -> Props) -> ReactDream
35 | const contramap = Component => propsPreprocessor =>
36 | ReactDream(doContramap(propsPreprocessor)(Component))
37 |
38 | // promap : Component -> (a -> Props) -> (Component -> Component) -> ReactDream
39 | const promap = Component => (propsPreprocessor, higherOrderComponent) =>
40 | ReactDream(doPromap(propsPreprocessor, higherOrderComponent)(Component))
41 |
42 | // CUSTOM HELPERS
43 | // ////////////////////////////////////////////////////////////////////////// //
44 |
45 | // addProps : Component -> (Props -> Props) -> ReactDream
46 | const addProps = Component => getPropsToAdd =>
47 | contramap(Component)(props => ({
48 | ...getPropsToAdd(props),
49 | ...props,
50 | }))
51 |
52 | // fork : Component -> (Component -> a) -> a
53 | const fork = Component => extractComponent => extractComponent(Component)
54 |
55 | // debug : Component -> () -> IO ReactDream
56 | const debug = Component => () => ReactDream(withDebugger(Component))
57 |
58 | // defaultProps : Component -> (Props) -> ReactDream
59 | const defaultProps = Component => props => ReactDream(recomposeDefaultProps(props)(Component))
60 |
61 | // log : Component -> (Props -> String) -> IO ReactDream
62 | const log = Component => messageFromProps => ReactDream(withLog(messageFromProps)(Component))
63 |
64 | // name : Component -> String -> ReactDream
65 | const name = Component => compose(map(Component), setDisplayName)
66 |
67 | // removeProps : Component -> (...Array) -> ReactDream
68 | const removeProps = Component => (...propsToRemove) =>
69 | contramap(Component)(props => {
70 | // Nasty but efficient
71 | const propsCopy = { ...props }
72 | propsToRemove.forEach(propName => {
73 | delete propsCopy[propName]
74 | })
75 | return propsCopy
76 | })
77 |
78 | // propTypes : Component -> (PropTypes) -> ReactDream
79 | const propTypes = Component => propTypesToSet => ReactDream(setPropTypes(propTypesToSet)(Component))
80 |
81 | // translate : Component -> (Props -> [Number]) -> ReactDream
82 | const translate = Component => getTranslateFromProps =>
83 | ReactDream(doTranslate(getTranslateFromProps)(Component))
84 |
85 | // rotate : Component -> (Props -> Number) -> ReactDream
86 | const rotate = Component => getRotateFromProps =>
87 | ReactDream(doRotate(getRotateFromProps)(Component))
88 |
89 | // scale : Component -> (Props -> Number) -> ReactDream
90 | const scale = Component => getScaleFromProps => ReactDream(doScale(getScaleFromProps)(Component))
91 |
92 | // style : Component -> (Props -> Style) -> ReactDream
93 | const style = Component => getStyleFromProps =>
94 | contramap(Component)(styleFromProps(getStyleFromProps))
95 |
96 | // TYPE
97 | // ////////////////////////////////////////////////////////////////////////// //
98 |
99 | // ReactDream : Component -> ReactDream
100 | const ReactDream = Component => ({
101 | Component,
102 |
103 | // Algebras
104 | ap: ap(Component),
105 | chain: chain(Component),
106 | concat: concat(Component),
107 | contramap: contramap(Component),
108 | map: map(Component),
109 | promap: promap(Component),
110 |
111 | // Custom helpers
112 | addProps: addProps(Component),
113 | debug: debug(Component),
114 | defaultProps: defaultProps(Component),
115 | fork: fork(Component),
116 | name: name(Component),
117 | log: log(Component),
118 | propTypes: propTypes(Component),
119 | removeProps: removeProps(Component),
120 | rotate: rotate(Component),
121 | scale: scale(Component),
122 | style: style(Component),
123 | translate: translate(Component),
124 | })
125 |
126 | ReactDream.of = ReactDream
127 |
128 | export const of = ReactDream.of
129 |
130 | export default ReactDream
131 |
--------------------------------------------------------------------------------
/src/createElementWithProps.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export default props => Component =>
4 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import ReactDream from './ReactDream'
2 |
3 | export { default as createElementWithProps } from './createElementWithProps'
4 |
5 | export { default as styleFromProps } from './styleFromProps'
6 |
7 | export { default as withStyleFromProps } from './withStyleFromProps'
8 |
9 | export { default as addProps } from './partialApplication/addProps'
10 | export { default as ap } from './partialApplication/ap'
11 | export { default as chain } from './partialApplication/chain'
12 | export { default as concat } from './partialApplication/concat'
13 | export { default as contramap } from './partialApplication/contramap'
14 | export { default as debug } from './partialApplication/debug'
15 | export { default as defaultProps } from './partialApplication/defaultProps'
16 | export { default as fork } from './partialApplication/fork'
17 | export { default as log } from './partialApplication/log'
18 | export { default as map } from './partialApplication/map'
19 | export { default as name } from './partialApplication/name'
20 | export { default as promap } from './partialApplication/promap'
21 | export { default as propTypes } from './partialApplication/propTypes'
22 | export { default as removeProps } from './partialApplication/removeProps'
23 | export { default as rotate } from './partialApplication/rotate'
24 | export { default as scale } from './partialApplication/scale'
25 | export { default as style } from './partialApplication/style'
26 | export { default as translate } from './partialApplication/translate'
27 |
28 | export const of = ReactDream
29 |
30 | export default ReactDream
31 |
--------------------------------------------------------------------------------
/src/internals/doAp.js:
--------------------------------------------------------------------------------
1 | // doAp : (Component -> Component) -> ReactDream -> Component
2 | export default higherOrderComponent => DreamComponent => DreamComponent.fork(higherOrderComponent)
3 |
--------------------------------------------------------------------------------
/src/internals/doConcat.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from 'react'
2 | import getDisplayName from 'recompose/getDisplayName'
3 | import setDisplayName from 'recompose/setDisplayName'
4 |
5 | // doConcat : Component -> Component -> Component
6 | export default ComponentA => ComponentB =>
7 | setDisplayName(
8 | getDisplayName(ComponentB).concat(getDisplayName(ComponentA))
9 | )(props =>
10 |
11 |
12 | )
13 |
--------------------------------------------------------------------------------
/src/internals/doContramap.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import compose from 'recompose/compose'
3 | import getDisplayName from 'recompose/getDisplayName'
4 | import isReferentiallyTransparentFunctionComponent from '../isReferentiallyTransparentFunctionComponent'
5 |
6 | // doContramap : (a -> Props) -> Component -> Component
7 | export default propsPreprocessor => Component => {
8 | const Enhanced = isReferentiallyTransparentFunctionComponent(Component)
9 | ? compose(Component, propsPreprocessor)
10 | : props =>
11 |
12 | Enhanced.displayName = getDisplayName(Component)
13 |
14 | return Enhanced
15 | }
16 |
--------------------------------------------------------------------------------
/src/internals/doMap.js:
--------------------------------------------------------------------------------
1 | // (Component -> Component) -> Component -> Component
2 | export default higherOrderComponent => Component => higherOrderComponent(Component)
3 |
--------------------------------------------------------------------------------
/src/internals/doPromap.js:
--------------------------------------------------------------------------------
1 | import compose from 'recompose/compose'
2 | import doContramap from './doContramap'
3 | import doMap from './doMap'
4 |
5 | // doPromap : ((a -> Props), (Component -> Component)) -> Component -> Component
6 | export default (propsPreprocessor, higherOrderComponent) =>
7 | compose(doMap(higherOrderComponent), doContramap(propsPreprocessor))
8 |
--------------------------------------------------------------------------------
/src/internals/doRotate.js:
--------------------------------------------------------------------------------
1 | import { compose } from 'recompose'
2 | import doContramap from './doContramap'
3 |
4 | const calculateTransform = oldTransform => rotation =>
5 | oldTransform ? `${oldTransform} rotate(${rotation}deg)` : `rotate(${rotation}deg)`
6 |
7 | // doRotate : (Props -> Number) -> Component -> Component
8 | export default getRotateFromProps => Component =>
9 | doContramap(props => ({
10 | ...props,
11 | style: {
12 | ...props.style,
13 | transform: compose(
14 | calculateTransform(props && props.style && props.style.transform),
15 | getRotateFromProps
16 | )(props),
17 | },
18 | }))(Component)
19 |
--------------------------------------------------------------------------------
/src/internals/doScale.js:
--------------------------------------------------------------------------------
1 | import { compose } from 'recompose'
2 | import doContramap from './doContramap'
3 |
4 | const calculateTransform = oldTransform => scaling =>
5 | oldTransform ? `${oldTransform} scale(${scaling})` : `scale(${scaling})`
6 |
7 | // doScale : (Props -> Number) -> Component -> Component
8 | export default getScaleFromProps => Component =>
9 | doContramap(props => ({
10 | ...props,
11 | style: {
12 | ...props.style,
13 | transform: compose(
14 | calculateTransform(props && props.style && props.style.transform),
15 | getScaleFromProps
16 | )(props),
17 | },
18 | }))(Component)
19 |
--------------------------------------------------------------------------------
/src/internals/doTranslate.js:
--------------------------------------------------------------------------------
1 | import { compose } from 'recompose'
2 | import doContramap from './doContramap'
3 |
4 | const calculateTransform = oldTransform => ([x, y, z]) => {
5 | switch (true) {
6 | case x != null && y != null && z != null:
7 | return oldTransform
8 | ? `${oldTransform} translate3D(${x}px, ${y}px, ${z}px)`
9 | : `translate3D(${x}px, ${y}px, ${z}px)`
10 |
11 | case x != null && y != null:
12 | return oldTransform
13 | ? `${oldTransform} translate(${x}px, ${y}px)`
14 | : `translate(${x}px, ${y}px)`
15 |
16 | case z != null:
17 | return oldTransform ? `${oldTransform} translateZ(${z}px)` : `translateZ(${z}px)`
18 |
19 | case y != null:
20 | return oldTransform ? `${oldTransform} translateY(${y}px)` : `translateY(${y}px)`
21 |
22 | case x != null:
23 | return oldTransform ? `${oldTransform} translateX(${x}px)` : `translateX(${x}px)`
24 | }
25 | }
26 |
27 | // doTranslate : (Props -> [Number]) -> Component -> Component
28 | export default getTranslateFromProps => Component =>
29 | doContramap(props => ({
30 | ...props,
31 | style: {
32 | ...props.style,
33 | transform: compose(
34 | calculateTransform(props && props.style && props.style.transform),
35 | getTranslateFromProps
36 | )(props),
37 | },
38 | }))(Component)
39 |
--------------------------------------------------------------------------------
/src/isReferentiallyTransparentFunctionComponent.js:
--------------------------------------------------------------------------------
1 | // This is now pointless and should be removed
2 | const isClassComponent = Component =>
3 | Boolean(
4 | Component &&
5 | Component.prototype &&
6 | typeof Component.prototype.render === "function"
7 | );
8 |
9 | const isReferentiallyTransparentFunctionComponent = Component =>
10 | Boolean(
11 | typeof Component === "function" &&
12 | !isClassComponent(Component) &&
13 | !Component.defaultProps &&
14 | !Component.contextTypes
15 | );
16 |
17 | export default isReferentiallyTransparentFunctionComponent;
18 |
--------------------------------------------------------------------------------
/src/partialApplication/addProps.js:
--------------------------------------------------------------------------------
1 | export default f => reactDream => reactDream.addProps(f)
2 |
--------------------------------------------------------------------------------
/src/partialApplication/ap.js:
--------------------------------------------------------------------------------
1 | export default f => apply => apply.ap(f)
2 |
--------------------------------------------------------------------------------
/src/partialApplication/chain.js:
--------------------------------------------------------------------------------
1 | export default f => chainable => chainable.chain(f)
2 |
--------------------------------------------------------------------------------
/src/partialApplication/concat.js:
--------------------------------------------------------------------------------
1 | export default f => x => x.concat(f)
2 |
--------------------------------------------------------------------------------
/src/partialApplication/contramap.js:
--------------------------------------------------------------------------------
1 | export default f => contravariant => contravariant.contramap(f)
2 |
--------------------------------------------------------------------------------
/src/partialApplication/debug.js:
--------------------------------------------------------------------------------
1 | export default f => reactDream => reactDream.debug(f)
2 |
--------------------------------------------------------------------------------
/src/partialApplication/defaultProps.js:
--------------------------------------------------------------------------------
1 | export default f => reactDream => reactDream.defaultProps(f)
2 |
--------------------------------------------------------------------------------
/src/partialApplication/fork.js:
--------------------------------------------------------------------------------
1 | export default f => reactDream => reactDream.fork(f)
2 |
--------------------------------------------------------------------------------
/src/partialApplication/log.js:
--------------------------------------------------------------------------------
1 | export default f => reactDream => reactDream.log(f)
2 |
--------------------------------------------------------------------------------
/src/partialApplication/map.js:
--------------------------------------------------------------------------------
1 | export default f => functor => functor.map(f)
2 |
--------------------------------------------------------------------------------
/src/partialApplication/name.js:
--------------------------------------------------------------------------------
1 | export default f => reactDream => reactDream.name(f)
2 |
--------------------------------------------------------------------------------
/src/partialApplication/promap.js:
--------------------------------------------------------------------------------
1 | export default f => profunctor => profunctor.promap(f)
2 |
--------------------------------------------------------------------------------
/src/partialApplication/propTypes.js:
--------------------------------------------------------------------------------
1 | export default f => x => x.propTypes(f)
2 |
--------------------------------------------------------------------------------
/src/partialApplication/removeProps.js:
--------------------------------------------------------------------------------
1 | export default f => reactDream => reactDream.removeProps(f)
2 |
--------------------------------------------------------------------------------
/src/partialApplication/rotate.js:
--------------------------------------------------------------------------------
1 | export default f => reactDream => reactDream.rotate(f)
2 |
--------------------------------------------------------------------------------
/src/partialApplication/scale.js:
--------------------------------------------------------------------------------
1 | export default f => reactDream => reactDream.scale(f)
2 |
--------------------------------------------------------------------------------
/src/partialApplication/style.js:
--------------------------------------------------------------------------------
1 | export default f => reactDream => reactDream.style(f)
2 |
--------------------------------------------------------------------------------
/src/partialApplication/translate.js:
--------------------------------------------------------------------------------
1 | export default f => reactDream => reactDream.translate(f)
2 |
--------------------------------------------------------------------------------
/src/styleFromProps.js:
--------------------------------------------------------------------------------
1 | // styleFromProps : (Props -> Style) -> (Props -> Props)
2 | export default getStyleFromProps => props => ({
3 | ...props,
4 | style: {
5 | ...getStyleFromProps(props),
6 | ...(props.style || {}),
7 | },
8 | })
9 |
--------------------------------------------------------------------------------
/src/withStyleFromProps.js:
--------------------------------------------------------------------------------
1 | import doContramap from './internals/doContramap'
2 | import styleFromProps from './styleFromProps'
3 |
4 | export default getStyleFromProps => doContramap(styleFromProps(getStyleFromProps))
5 |
--------------------------------------------------------------------------------
/test/ReactDream.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { create } from 'react-test-renderer'
3 | import ReactDream, { of } from '../src/ReactDream'
4 | import { example, suite } from './dsl'
5 |
6 | const Target = x => x
7 |
8 | export default suite(
9 | 'ReactDream',
10 |
11 | example(
12 | 'wraps the Component',
13 |
14 | () => ReactDream(Target).Component,
15 |
16 | Target
17 | ),
18 |
19 | ...suite(
20 | 'Functor',
21 |
22 | ...suite(
23 | 'map',
24 |
25 | example(
26 | 'runs the Component through the HoC and puts it back in a ReactDream',
27 |
28 | () => {
29 | const Component = 1
30 | const higherOrderComponent = x => x + 1
31 | const EnhancedReactDreamComponent = ReactDream(Component).map(higherOrderComponent)
32 |
33 | return EnhancedReactDreamComponent.Component
34 | },
35 |
36 | 2
37 | )
38 | )
39 | ),
40 |
41 | ...suite(
42 | 'Apply',
43 | ...suite(
44 | 'ap',
45 | example(
46 | 'passes the argument to the component',
47 | () => {
48 | const ReactDreamComponent = ReactDream(x => !x)
49 |
50 | return ReactDreamComponent.ap(ReactDream(false)).Component
51 | },
52 | true
53 | )
54 | )
55 | ),
56 |
57 | ...suite(
58 | 'Applicative',
59 | ...suite(
60 | 'ReactDream.of',
61 | example(
62 | 'wraps the Component',
63 | () => ReactDream.of(Target).Component,
64 | Target
65 | )
66 | ),
67 |
68 | ...suite(
69 | 'of - named export',
70 | example(
71 | 'wraps the Component',
72 | () => of(Target).Component,
73 | Target
74 | )
75 | )
76 | ),
77 |
78 | ...suite(
79 | 'Contravariant',
80 |
81 | ...suite(
82 | 'contramap',
83 |
84 | ...suite(
85 | 'is a referentially transparent function component',
86 | example(
87 | 'pre composes the propsPreprocesssor',
88 | () => {
89 | const ReferentiallyTransparentComponent = x => !x
90 | const propsPreprocessor = () => true
91 | const ReactDreamComponent = ReactDream(ReferentiallyTransparentComponent)
92 |
93 | return ReactDreamComponent.contramap(propsPreprocessor).Component()
94 | },
95 | false
96 | ),
97 |
98 | ...suite(
99 | 'it has name',
100 | example(
101 | 'preserves the name as displayName',
102 | () => {
103 | function ReferentiallyTransparentComponent(x) {
104 | return x
105 | }
106 |
107 | const ReactDreamComponent = ReactDream(ReferentiallyTransparentComponent)
108 |
109 | return ReactDreamComponent.contramap(x => x).Component.displayName
110 | },
111 | 'ReferentiallyTransparentComponent'
112 | )
113 | ),
114 |
115 | ...suite(
116 | 'it has displayName',
117 | example(
118 | 'preserves it',
119 | () => {
120 | const ReferentiallyTransparentComponent = x => x
121 | ReferentiallyTransparentComponent.displayName = 'Casablanca'
122 |
123 | const ReactDreamComponent = ReactDream(ReferentiallyTransparentComponent)
124 |
125 | return ReactDreamComponent.contramap(x => x).Component.displayName
126 | },
127 | 'Casablanca'
128 | )
129 | )
130 | ),
131 |
132 | ...suite(
133 | 'is not referentially transparent',
134 | example(
135 | 'returns a new component that wraps building the inner component with the propsPreprocesssor filtering the props',
136 |
137 | () => {
138 | class NotReferentiallyTransparent extends Component {
139 | constructor() {
140 | super()
141 |
142 | this.state = {}
143 | }
144 |
145 | render() {
146 | return (
147 |