├── tests ├── .eslintrc └── index-test.js ├── .gitignore ├── nwb.config.js ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE ├── package.json ├── src └── index.js ├── demo └── src │ └── index.js ├── .all-contributorsrc └── README.md /tests/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "mocha": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /coverage 2 | /demo/dist 3 | /es 4 | /lib 5 | /node_modules 6 | /umd 7 | npm-debug.log* 8 | -------------------------------------------------------------------------------- /nwb.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | type: 'react-component', 3 | npm: { 4 | esModules: true, 5 | umd: false 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: node_js 4 | node_js: 5 | - 8 6 | 7 | before_install: 8 | - npm install codecov.io coveralls 9 | 10 | after_success: 11 | - cat ./coverage/lcov.info | ./node_modules/codecov.io/bin/codecov.io.js 12 | - cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js 13 | 14 | branches: 15 | only: 16 | - master 17 | -------------------------------------------------------------------------------- /tests/index-test.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect' 2 | import React from 'react' 3 | import {render, unmountComponentAtNode} from 'react-dom' 4 | 5 | import Component from 'src/' 6 | 7 | describe('Component', () => { 8 | let node 9 | 10 | beforeEach(() => { 11 | node = document.createElement('div') 12 | }) 13 | 14 | afterEach(() => { 15 | unmountComponentAtNode(node) 16 | }) 17 | 18 | it('displays a welcome message', () => { 19 | render(, node, () => { 20 | expect(node.innerHTML).toContain('Welcome to React components') 21 | }) 22 | }) 23 | }) 24 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Prerequisites 2 | 3 | [Node.js](http://nodejs.org/) >= 6 must be installed. 4 | 5 | ## Installation 6 | 7 | - Running `npm install` in the component's root directory will install everything you need for development. 8 | 9 | ## Demo Development Server 10 | 11 | - `npm start` will run a development server with the component's demo app at [http://localhost:3000](http://localhost:3000) with hot module reloading. 12 | 13 | ## Running Tests 14 | 15 | - `npm test` will run the tests once. 16 | 17 | - `npm run test:coverage` will run the tests and produce a coverage report in `coverage/`. 18 | 19 | - `npm run test:watch` will run the tests on every change. 20 | 21 | ## Building 22 | 23 | - `npm run build` will build the component for publishing to npm and also bundle the demo app. 24 | 25 | - `npm run clean` will delete built resources. 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Astrocoders 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 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "epitath", 3 | "version": "1.0.0-beta.2", 4 | "description": "Compose HOCs imperatively like async/await. No callback hell!", 5 | "main": "lib/index.js", 6 | "module": "es/index.js", 7 | "files": [ 8 | "css", 9 | "es", 10 | "lib", 11 | "umd" 12 | ], 13 | "scripts": { 14 | "contributors:add": "all-contributors add", 15 | "contributors:generate": "all-contributors generate", 16 | "build": "nwb build-react-component", 17 | "clean": "nwb clean-module && nwb clean-demo", 18 | "prepublishOnly": "npm run build", 19 | "start": "nwb serve-react-demo", 20 | "test": "nwb test-react", 21 | "test:coverage": "nwb test-react --coverage", 22 | "test:watch": "nwb test-react --server", 23 | "deploy": "gh-pages -d demo/dist" 24 | }, 25 | "dependencies": { 26 | "immutagen": "^1.0.7" 27 | }, 28 | "peerDependencies": { 29 | "react": "16.x" 30 | }, 31 | "devDependencies": { 32 | "all-contributors-cli": "^5.4.0", 33 | "formik": "^1.3.0", 34 | "gh-pages": "^1.2.0", 35 | "nwb": "0.23.x", 36 | "react": "^16.4.2", 37 | "react-dom": "^16.4.2" 38 | }, 39 | "author": "Astrocoders", 40 | "homepage": "https://github.com/Astrocoders/epitath", 41 | "license": "MIT", 42 | "repository": "", 43 | "keywords": [ 44 | "react-component", 45 | "hocs", 46 | "render-props", 47 | "compose" 48 | ] 49 | } 50 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import immutagen from 'immutagen' 3 | 4 | const compose = ({ next, value }) => next 5 | ? React.cloneElement(value, null, values => compose(next(values))) 6 | : value 7 | 8 | export default Component => { 9 | const original = Component.prototype.render 10 | const displayName = `EpitathContainer(${Component.displayName || 'anonymous'})` 11 | 12 | if (!original) { 13 | const generator = immutagen(Component) 14 | 15 | return Object.assign(function Epitath(props) { 16 | return compose(generator(props)) 17 | }, { displayName }) 18 | } 19 | 20 | Component.prototype.render = function render() { 21 | // Since we are calling a new function to be called from here instead of 22 | // from a component class, we need to ensure that the render method is 23 | // invoked against `this`. We only need to do this binding and creation of 24 | // this function once, so we cache it by adding it as a property to this 25 | // new render method which avoids keeping the generator outside of this 26 | // method's scope. 27 | if (!render.generator) { 28 | render.generator = immutagen(original.bind(this)) 29 | } 30 | 31 | return compose(render.generator(this.props)) 32 | } 33 | 34 | return class EpitathContainer extends React.Component { 35 | static displayName = displayName 36 | render() { 37 | return 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /demo/src/index.js: -------------------------------------------------------------------------------- 1 | import React, {Fragment} from 'react' 2 | import {render} from 'react-dom' 3 | import {Formik} from 'formik' 4 | 5 | import epitath from '../../src' 6 | 7 | class Query extends React.Component { 8 | state = {loading: true, data: null} 9 | 10 | componentDidMount() { 11 | setTimeout(() => { 12 | this.setState({ 13 | loading: false, 14 | data: { 15 | user: {id: '000', name: 'Nikolas Tesla', email: 'nikolas@tesla.com'}, 16 | }, 17 | }) 18 | }, 2000) 19 | } 20 | 21 | render() { 22 | return this.props.children(this.state) 23 | } 24 | } 25 | 26 | class Time extends React.Component { 27 | state = {time: new Date()} 28 | 29 | componentDidMount() { 30 | setInterval(() => { 31 | this.setState({ 32 | time: new Date(), 33 | }) 34 | }, 1000) 35 | } 36 | 37 | render() { 38 | return this.props.children(this.state) 39 | } 40 | } 41 | 42 | function WrapFormik({ children, ...props}){ return } 43 | 44 | const App = epitath(function*() { 45 | console.log('Rendering again!'); 46 | const {loading, data} = yield 47 | const {time} = yield