├── .babelrc ├── src ├── __snapshots__ │ └── index.test.js.snap ├── index.test.js └── index.js ├── .gitignore ├── README.md └── package.json /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "env", 4 | "react" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /src/__snapshots__/index.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`fold-render-props basic children 1`] = ` 4 |
 5 |   {
 6 |   "name": "⒞<big>⒝⒜CHILDREN⒜⒝</big>⒞⒞<big>⒝⒜CHILDREN⒜⒝</big>⒞⒞<big>⒝⒜CHILDREN⒜⒝</big>⒞"
 7 | }
 8 | 
9 | `; 10 | 11 | exports[`fold-render-props basic render 1`] = ` 12 |
13 |   {
14 |   "name": "⒞<big>⒝⒜RENDER⒜⒝</big>⒞⒞<big>⒝⒜RENDER⒜⒝</big>⒞⒞<big>⒝⒜RENDER⒜⒝</big>⒞"
15 | }
16 | 
17 | `; 18 | 19 | exports[`fold-render-props basic testRenderProp 1`] = ` 20 |
21 |   {
22 |   "name": "⒞<big>⒝⒜TESTRENDERPROP⒜⒝</big>⒞⒞<big>⒝⒜TESTRENDERPROP⒜⒝</big>⒞⒞<big>⒝⒜TESTRENDERPROP⒜⒝</big>⒞"
23 | }
24 | 
25 | `; 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Node template 3 | # Logs 4 | logs 5 | *.log 6 | npm-debug.log* 7 | yarn-debug.log* 8 | yarn-error.log* 9 | 10 | # Runtime data 11 | pids 12 | *.pid 13 | *.seed 14 | *.pid.lock 15 | 16 | # Directory for instrumented libs generated by jscoverage/JSCover 17 | lib-cov 18 | 19 | # Coverage directory used by tools like istanbul 20 | coverage 21 | 22 | # nyc test coverage 23 | .nyc_output 24 | 25 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 26 | .grunt 27 | 28 | # Bower dependency directory (https://bower.io/) 29 | bower_components 30 | 31 | # node-waf configuration 32 | .lock-wscript 33 | 34 | # Compiled binary addons (https://nodejs.org/api/addons.html) 35 | build/Release 36 | 37 | # Dependency directories 38 | node_modules/ 39 | jspm_packages/ 40 | 41 | # Typescript v1 declaration files 42 | typings/ 43 | 44 | # Optional npm cache directory 45 | .npm 46 | 47 | # Optional eslint cache 48 | .eslintcache 49 | 50 | # Optional REPL history 51 | .node_repl_history 52 | 53 | # Output of 'npm pack' 54 | *.tgz 55 | 56 | # Yarn Integrity file 57 | .yarn-integrity 58 | 59 | # dotenv environment variables file 60 | .env 61 | 62 | # next.js build output 63 | .next 64 | ### Example user template template 65 | ### Example user template 66 | 67 | # IntelliJ project files 68 | .idea 69 | *.iml 70 | out 71 | gen 72 | 73 | yarn.lock 74 | dist/ 75 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fold-render-props 2 | Fold multiple render prop components into a single component. 3 | 4 | 5 | [![npm version](https://badge.fury.io/js/fold-render-props.svg)](https://badge.fury.io/js/fold-render-props) 6 | [![Build Status](https://travis-ci.org/tkh44/fold-render-props.svg?branch=master)](https://travis-ci.org/tkh44/fold-render-props) 7 | [![codecov](https://codecov.io/gh/tkh44/fold-render-props/branch/master/graph/badge.svg)](https://codecov.io/gh/tkh44/fold-render-props) 8 | 9 | 10 | ## Install 11 | 12 | ```bash 13 | npm i fold-render-props -S 14 | ``` 15 | 16 | ## Basic Example 17 | 18 | ```javascript 19 | import folder from 'fold-render-props' 20 | 21 | const ComponentA = props => { 22 | return props.children({ 23 | name: props.name.toUpperCase() 24 | }) 25 | } 26 | 27 | const ComponentB = props => 28 | props.children({ 29 | name: props.name.big() 30 | }) 31 | 32 | 33 | const ComponentC = props => { 34 | return props.children({ 35 | name: props.name.repeat(3) 36 | }) 37 | } 38 | 39 | const Folder = folder([ 40 | (result, render) => ( 41 | 42 | ), 43 | (result, render) => ( 44 | 45 | ), 46 | (result, render) => ( 47 | 48 | ) 49 | ]) 50 | 51 | // Usage 52 | const MyComponent = (props) => ( 53 |
54 | {r =>
{JSON.stringify(r)}
}
55 |
56 | ) 57 | ``` 58 | 59 | *This renders* 60 | 61 | ``` 62 |
63 |
64 |     { "name": "⒞⒝⒜THING⒜⒝⒞⒞⒝⒜THING⒜⒝⒞⒞⒝⒜THING⒜⒝⒞" }
65 |   
66 |
67 | ``` 68 | -------------------------------------------------------------------------------- /src/index.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import renderer from 'react-test-renderer' 3 | import folder from './index' 4 | 5 | const TEST_RENDER_PROP = 'testRenderProp' 6 | 7 | const ComponentA = props => { 8 | return (props.children || props.render || props[TEST_RENDER_PROP])({ 9 | name: props.name.toUpperCase() 10 | }) 11 | } 12 | 13 | ComponentA.displayName = 'A' 14 | 15 | const ComponentB = props => { 16 | return (props.children || props.render || props[TEST_RENDER_PROP])({ 17 | name: props.name.big() 18 | }) 19 | } 20 | 21 | ComponentB.displayName = 'B' 22 | 23 | const ComponentC = props => { 24 | return (props.children || props.render || props[TEST_RENDER_PROP])({ 25 | name: props.name.repeat(3) 26 | }) 27 | } 28 | ComponentC.displayName = 'C' 29 | 30 | describe('fold-render-props', () => { 31 | const propNames = ['render', 'children', TEST_RENDER_PROP] 32 | propNames.forEach(renderProp => { 33 | const Folder = folder( 34 | [ 35 | (result, render) => 36 | React.createElement(ComponentA, { 37 | name: '⒜' + result.name + '⒜', 38 | [renderProp]: render 39 | }), 40 | (result, render) => 41 | React.createElement(ComponentB, { 42 | name: '⒝' + result.name + '⒝', 43 | [renderProp]: render 44 | }), 45 | (result, render) => 46 | React.createElement(ComponentC, { 47 | name: '⒞' + result.name + '⒞', 48 | [renderProp]: render 49 | }) 50 | ], 51 | { 52 | renderPropName: renderProp 53 | } 54 | ) 55 | 56 | test('basic ' + renderProp, () => { 57 | const tree = renderer 58 | .create( 59 | React.createElement(Folder, { 60 | name: renderProp, 61 | [renderProp]: result => { 62 | return
{JSON.stringify(result, null, 2)}
63 | } 64 | }) 65 | ) 66 | .toJSON() 67 | expect(tree).toMatchSnapshot() 68 | }) 69 | }) 70 | }) 71 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fold-render-props", 3 | "version": "0.0.2", 4 | "description": 5 | "Fold multiple render prop components into a single component.", 6 | "browser": "dist/fold-render-props.umd.js", 7 | "module": "dist/fold-render-props.m.js", 8 | "main": "dist/fold-render-props.js", 9 | "source": "src/index.js", 10 | "scripts": { 11 | "build": "microbundle", 12 | "dev": "microbundle watch", 13 | "test": "jest --ci", 14 | "release": 15 | "npm run test && npm run build && npm version patch && npm publish && git push --tags" 16 | }, 17 | "keywords": ["react", "render", "render-props"], 18 | "author": "Kye Hohenberger", 19 | "license": "MIT", 20 | "peerDependencies": { 21 | "react": ">=14" 22 | }, 23 | "devDependencies": { 24 | "babel-eslint": "^8.2.1", 25 | "babel-jest": "^22.2.0", 26 | "babel-preset-env": "^1.6.1", 27 | "babel-preset-react": "^6.24.1", 28 | "eslint": "^4.17.0", 29 | "eslint-config-prettier": "^2.9.0", 30 | "eslint-config-react": "^1.1.7", 31 | "eslint-config-standard": "^11.0.0-beta.0", 32 | "eslint-config-standard-react": "^5.0.0", 33 | "eslint-plugin-import": "^2.8.0", 34 | "eslint-plugin-jest": "^21.7.0", 35 | "eslint-plugin-node": "^6.0.0", 36 | "eslint-plugin-prettier": "^2.6.0", 37 | "eslint-plugin-react": "^7.6.1", 38 | "eslint-plugin-standard": "^3.0.1", 39 | "jest": "^22.2.1", 40 | "microbundle": "^0.4.3", 41 | "prettier": "^1.10.2", 42 | "react": "^16.2.0", 43 | "react-dom": "^16.2.0", 44 | "react-test-renderer": "^16.2.0", 45 | "standard": "^10.0.2" 46 | }, 47 | "files": ["dist", "src"], 48 | "directories": { 49 | "test": "tests" 50 | }, 51 | "eslintConfig": { 52 | "extends": ["standard", "standard-react", "prettier", "prettier/react"], 53 | "plugins": ["jest", "prettier"], 54 | "parser": "babel-eslint", 55 | "rules": { 56 | "prettier/prettier": [ 57 | "error", 58 | { 59 | "singleQuote": true, 60 | "semi": false 61 | } 62 | ], 63 | "react/prop-types": 0, 64 | "react/no-unused-prop-types": 0, 65 | "standard/computed-property-even-spacing": 0, 66 | "no-template-curly-in-string": 0 67 | }, 68 | "env": { 69 | "jest/globals": true 70 | } 71 | }, 72 | "standard": { 73 | "parser": "babel-eslint", 74 | "ignore": ["/dist/"] 75 | }, 76 | "jest": { 77 | "transform": { 78 | "^.+\\.js?$": "babel-jest" 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | // Basic idea is to compose several components that use a 2 | // children/render fn prop. 3 | 4 | /* 5 | type renderFn = (result: Any, renderFn) => React.Element<*> 6 | 7 | ([ 8 | renderFn: renderFn 9 | ]) 10 | */ 11 | export default function foldRenderProps( 12 | list, 13 | { renderPropName = 'children' } = {} 14 | ) { 15 | /* 16 | // Defined 17 | const Folder = folder([ 18 | (result, render) => ( 19 | 20 | ), 21 | (result, render) => ( 22 | 23 | ), 24 | (result, render) => ( 25 | 26 | ) 27 | ]) 28 | 29 | // Render the following 30 | {r =>
{JSON.stringify(r)}
} 31 | 32 | props: 33 | - name = "bob" 34 | - children = result =>
{JSON.stringify(result)}
35 | 36 | 37 | render1}/> 38 | render2}/> 39 | render3}/> 40 | 41 | // In order to correctly order render fn's we need to reverse the list of renders 42 | // The basic idea is to reverse and turn the structure "inside out". 43 | 44 | // List 45 | [ 46 | (result, render) => ( 47 | 48 | ), 49 | (result, render) => ( 50 | 51 | ), 52 | (result, render) => ( 53 | 54 | ) 55 | ] 56 | 57 | // Turn inside out 58 | // Render the previous return value of the reducer as a child 59 | 60 | // Reducer Default Value (value of `Child` on first iteration) 61 | Folder0 = ({ children, ...rest }) => children(rest) 62 | 63 | // First item in list 64 | Child = Folder0 65 | renderer = (result, render) => ( 66 | 67 | ) 68 | Folder1 = (props) => 69 | renderer(x, props.children)} /> 70 | 71 | // Second item in list 72 | Child = Folder1 73 | renderer = (result, render) => ( 74 | 75 | ) 76 | Folder2 = (props) => 77 | renderer(x, props.children)} /> 78 | 79 | // Third item in list 80 | Child = Folder2 81 | renderer = (result, render) => ( 82 | 83 | ) 84 | Folder3 = (props) => 85 | renderer(x, props.children)} /> 86 | 87 | // End of list reached and Folder3 is returned 88 | 89 | Because we know that Child is always going to be a pure function we are able to remove the 90 | intermediary components by calling the SFCs as a standard js functions. 91 | 92 | return Child(prop) 93 | 94 | vs 95 | 96 | return 97 | 98 | All together our Folder looks like the following 99 | 100 | props => { 101 | return ( 102 | 103 | {resultA => ( 104 | 105 | {resultB => ( 106 | 107 | {resultC => ( 108 | props.children(resultC) 109 | )} 110 | 111 | )} 112 | 113 | )} 114 | 115 | ) 116 | } 117 | */ 118 | const renderNameList = [renderPropName, 'render', 'children'] 119 | const getRenderFnName = props => { 120 | for (let i = 0; i < renderNameList.length; ++i) { 121 | if (typeof props[renderNameList[i]] === 'function') { 122 | return renderNameList[i] 123 | } 124 | } 125 | 126 | return 'children' 127 | } 128 | 129 | const UnwrapChildren = props => { 130 | const fn = props[getRenderFnName(props)] 131 | return fn(props) 132 | } 133 | 134 | const reducer = (Child, renderer) => { 135 | const Folder = props => { 136 | const name = getRenderFnName(props) 137 | return Child( 138 | Object.assign( 139 | {}, 140 | props, 141 | { children: null }, 142 | { 143 | [name]: x => { 144 | return renderer(x, props[name]) 145 | } 146 | } 147 | ) 148 | ) 149 | } 150 | return Folder 151 | } 152 | 153 | return list.reduce(reducer, UnwrapChildren) 154 | } 155 | --------------------------------------------------------------------------------