├── .babelrc ├── .eslintrc.js ├── .gitignore ├── LICENSE ├── README.md ├── circle.yml ├── lib ├── __tests__ │ ├── compose-test.js │ └── connect-test.js ├── config.js ├── connect.js ├── getDisplayName.js └── index.js ├── package-lock.json ├── package.json └── src ├── __tests__ ├── .eslintrc.js ├── compose-test.js └── connect-test.js ├── config.js ├── connect.js ├── getDisplayName.js └── index.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ "react", "stage-0", "es2015" ], 3 | "plugins": [ "lodash" ], 4 | } 5 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: 'airbnb', 3 | parser: 'babel-eslint', 4 | plugins: [ 5 | 'react', 6 | ], 7 | rules: { 8 | 'react/jsx-filename-extension': [1, { 9 | extensions: ['.js', '.jsx'], 10 | }], 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules 28 | 29 | *.sw* 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 UniversalAvenue 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 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![React Compose](https://s3.amazonaws.com/f.cl.ly/items/1y000n0q2a2n0L2Y243S/react-compose-logo@2x.png) 2 | [![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/) 3 | [![Circle CI](https://circleci.com/gh/UniversalAvenue/react-compose/tree/master.svg?style=svg)](https://circleci.com/gh/UniversalAvenue/react-compose/tree/master) 4 | 5 | **React-compose** allows you to encapsulate component logic into smaller, 6 | reusable functions, which in turn can be combined back into component. The 7 | fundamental idea is that React components has a way of becoming bloated with, 8 | often repeated, logic. This lib provides you with a set of tools to avoid that. 9 | 10 | The encapsulated pieces will be easily testable, either because they are 11 | constant or since their functionality has a more narrow scope than a 12 | corresponding component would have. 13 | 14 | The other aspect of **react-compose** is based upon the fact that whenever you 15 | create a React component, you also create an api for it as well. It is 16 | essential, for any large scale project that this api is well formed and consistent 17 | across the application. Most components should also be extendable too, which is 18 | why, significant care is needed to make sure that each component doesn't break 19 | these rules. 20 | 21 | Let's show a simple example of extendablity: 22 | 23 | ```javascript 24 | const ButtonComponent = props => { 25 | const { 26 | onClick, 27 | label, 28 | } = props; 29 | return ; 30 | }; 31 | ``` 32 | 33 | Now if a developer would like to manipulate the style of `ButtonComponent` from 34 | the outside, it would have to be changed accordingly: 35 | 36 | ```javascript 37 | const ButtonComponent = props => { 38 | const { 39 | onClick, 40 | style, 41 | label, 42 | } = props; 43 | return ; 44 | }; 45 | ``` 46 | 47 | On the other hand, if all props should be passed down to the `button` element, 48 | the following is much more useful: 49 | 50 | ```javascript 51 | const ButtonComponent = props => { 52 | const { 53 | label, 54 | } = props; 55 | return ; 56 | }; 57 | ``` 58 | With **react-compose**, the above would be written as: 59 | 60 | ```javascript 61 | const labelToChildren = ({ label }) => ({ children: label }); 62 | 63 | const ButtonComponent = compose(labelToChildren)('button'); 64 | ``` 65 | Leaving much less room for breaking the rules of extendability and resuability. 66 | The CustomComponent should essentially work as you would expect that the basic 67 | html elements does, `ButtonComponent` ~ `button`, beyond of course the added 68 | behavior. 69 | 70 | As an extra bonus, it is also more straight forward to test the encapsulated 71 | behavior rather than the component as a whole. 72 | 73 | ```javascript 74 | describe('labelToChildren', () => { 75 | it('should pass whatever input label as children', () => { 76 | expect(labelToChildren({ label: 'string' }).children).toEqual('string'); 77 | }); 78 | }); 79 | ``` 80 | 81 | Finally, the heart of **react-compose**, is finding those elementary patterns 82 | that are present in your application. In this case, we can create a nice higher 83 | order function for the `labelToChildren` logic. 84 | 85 | ```javascript 86 | const mixProp = (from, to) => props => ({ [to]: props[from] }); 87 | const labelToChildren = mixProp('label', 'children'); 88 | ``` 89 | 90 | ## Installation 91 | 92 | Install package, and check that you are using a matching version of React (^0.14) 93 | 94 | ```bash 95 | npm install -s react-compose 96 | ``` 97 | 98 | ## API 99 | 100 | Example api usage: 101 | 102 | ```javascript 103 | import { compose } from 'react-compose'; 104 | 105 | const constantProper = { 106 | age: 15, 107 | }; 108 | 109 | const dynamicProper = props => { 110 | return { 111 | children: `The cat is ${props.age} years old`, 112 | }; 113 | }; 114 | 115 | const Cat = compose(constantProper, dynamicProper)('p'); 116 | 117 | // =>

The cat is 15 years old

; 118 | ``` 119 | 120 | Specialized style composing 121 | 122 | ```javascript 123 | import { compose, styles } from 'react-compose'; 124 | 125 | const constantStyle = { 126 | background: 'red', 127 | }; 128 | const dynamicStyle = ({ isActive }) => (!isActive && { 129 | display: 'none', 130 | }); 131 | 132 | const Component = compose(styles(constantStyle, dynamicStyle))('p'); 133 | 134 | return (props) => { 135 | return Some text; 136 | }; 137 | ``` 138 | 139 | Stacking custom components 140 | 141 | ```javascript 142 | import { compose } from 'react-compose'; 143 | 144 | const Cat = props => { 145 | return

The cat is {props.age} years old

; 146 | }; 147 | 148 | const injectAge = { 149 | age: 5, 150 | }; 151 | 152 | const Composed = compose(injectAge)(Cat); 153 | 154 | // =>

The cat is 5 years old

155 | ``` 156 | 157 | Composing complex children values 158 | 159 | ```javascript 160 | import { compose, children } from 'react-compose'; 161 | 162 | const AgeInfo = props => { 163 | return

Age: {props.age} years

; 164 | }; 165 | 166 | const LengthInfo = props => { 167 | return

Length: {props.length} cm

; 168 | }; 169 | 170 | const HeightInfo = props => { 171 | return

Height: {props.height} cm

; 172 | }; 173 | 174 | const Info = compose(children(AgeInfo, LengthInfo, HeightInfo))('div'); 175 | 176 | const dogData = { 177 | age: 5, 178 | length: 250, 179 | height: 150, 180 | }; 181 | 182 | const DogInfo = compose(dogData)(Info); 183 | 184 | // =>
185 | //

Age: 5

186 | //

Length: 250

187 | //

Height: 150

188 | //
189 | ``` 190 | 191 | Composing classNames, using the awesome [classnames](https://github.com/JedWatson/classnames) lib 192 | 193 | ```javascript 194 | import { compose, classNames } from 'react-compose'; 195 | 196 | const btnClassNames = classNames('btn', 197 | ({ pressed }) => pressed && 'btn-pressed', 198 | ({ hover }) => hover && 'btn-hover'); 199 | 200 | const Button = compose(btnClassNames)('button'); 201 | 202 | // pressed: true =>