├── .babelrc
├── .editorconfig
├── .eslintrc
├── .gitignore
├── .prettierrc
├── LICENSE
├── README.md
├── example
├── README.md
├── package.json
├── public
│ ├── index.html
│ └── manifest.json
└── src
│ ├── App.js
│ ├── components
│ ├── Fetch.js
│ └── Toggle.js
│ └── index.js
├── package.json
├── rollup.config.js
├── src
├── __tests__
│ └── index.js
├── index.js
└── utils.js
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["env", {
4 | "modules": false
5 | }],
6 | "stage-0",
7 | "react"
8 | ],
9 | "plugins": [
10 | "external-helpers"
11 | ]
12 | }
13 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "env": {
4 | "es6": true
5 | },
6 | "extends": ["plugin:prettier/recommended"],
7 | "plugins": ["react"]
8 | }
9 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # See https://help.github.com/ignore-files/ for more about ignoring files.
3 |
4 | # dependencies
5 | node_modules
6 |
7 | # builds
8 | build
9 | dist
10 |
11 | # misc
12 | .DS_Store
13 | .idea
14 | .env
15 | .env.local
16 | .env.development.local
17 | .env.test.local
18 | .env.production.local
19 |
20 | npm-debug.log*
21 | yarn-debug.log*
22 | yarn-error.log*
23 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true
3 | }
4 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Paul Matyukov
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 | # react-with
2 |
3 | ⚡️Awesome render props components creation
4 |
5 | [](https://www.npmjs.com/package/react-with)
6 |
7 | Features
8 | - simple api
9 | - lifecycle support
10 |
11 | ## Install
12 | ```bash
13 | yarn add react-with --dev
14 | or
15 | npm install --save react-with
16 | ```
17 |
18 | ## Usage
19 | Consider Toggle component example
20 | ```jsx
21 | import React from 'react';
22 | import With from 'react-with';
23 |
24 | const onToggle = ({ state, setState }, event) =>
25 | setState(prevState => ({ on: !prevState.on }));
26 |
27 | const Toggle = ({ children, initial }) => (
28 |
29 | {({ on, toggle }) => (
30 |
31 | )}
32 |
33 | );
34 |
35 | ```
36 |
37 | Props:
38 |
39 | *state* - initial state object
40 |
41 |
42 | *lifecycle* - object with React lifecycle hooks (componentDidMount, componentWillUnmount, etc) (see Fetch example)
43 |
44 |
45 | *render* - if there is a necessity to use instead of children function
46 |
47 | Each function passed to With Component receives *self* as a first argument.
48 |
49 | *self* is an object and contains current state, setState, and other passed props to With component
50 |
51 | ## Examples
52 | #### Toggle
53 | ```jsx
54 | import React from 'react';
55 |
56 | import With from 'react-with';
57 |
58 | const onToggle = ({ state, setState }) =>
59 | setState(prevState => ({ on: !prevState.on }));
60 |
61 | const Toggle = ({ children, initial }) => (
62 |
63 | {children}
64 |
65 | );
66 |
67 | // use
68 |
69 | {({ on, toggle }) => (
70 |
71 | )}
72 |
73 |
74 |
75 | ```
76 |
77 | #### Fetch
78 |
79 | ```jsx
80 | import React from 'react';
81 | import PropTypes from 'prop-types';
82 |
83 | import With from 'react-with';
84 |
85 | const componentDidMount = ({ setState, url }) => {
86 | setState({ loading: true });
87 |
88 | fetch(url)
89 | .then(response => response.json())
90 | .then(json =>
91 | setState({
92 | result: json,
93 | loading: false
94 | })
95 | );
96 | };
97 |
98 | const Fetch = ({ url, children, render }) => (
99 |
107 | {children}
108 | );
109 |
110 | // use
111 | const url = 'https://jsonplaceholder.typicode.com/comments';
112 |
113 | {({ loading, result }) =>
114 | loading ? (
115 | 'Loading...'
116 | ) : (
117 | {result.map(item => (
118 |
{item.name}
)
119 | )}
120 | )
121 | }
122 |
123 | ```
124 |
125 | ## License
126 |
127 | MIT © [matpaul](https://github.com/matpaul)
128 |
--------------------------------------------------------------------------------
/example/README.md:
--------------------------------------------------------------------------------
1 | This project was bootstrapped with [Create React App](https://github.com/facebookincubator/create-react-app).
2 |
3 | Below you will find some information on how to perform common tasks.
4 | You can find the most recent version of this guide [here](https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md).
5 |
--------------------------------------------------------------------------------
/example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-with-example",
3 | "homepage": "https://matpaul.github.io/react-with",
4 | "version": "0.0.0",
5 | "private": true,
6 | "license": "MIT",
7 | "dependencies": {
8 | "prop-types": "^15.6.1",
9 | "react": "^16.2.0",
10 | "react-dom": "^16.2.0",
11 | "react-with": "link:..",
12 | "react-scripts": "^1.1.1"
13 | },
14 | "scripts": {
15 | "start": "react-scripts start",
16 | "build": "react-scripts build",
17 | "test": "react-scripts test --env=jsdom",
18 | "eject": "react-scripts eject"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/example/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | react-with
11 |
12 |
13 |
14 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/example/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "react-with",
3 | "name": "react-with",
4 | "start_url": "./index.html",
5 | "display": "standalone",
6 | "theme_color": "#000000",
7 | "background_color": "#ffffff"
8 | }
9 |
--------------------------------------------------------------------------------
/example/src/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import Toggle from './components/Toggle';
4 | import Fetch from './components/Fetch';
5 |
6 | const url = 'https://jsonplaceholder.typicode.com/comments';
7 |
8 | const App = () => (
9 |
10 |
Toggle Example
11 |
12 | {({ on, toggle }) => (
13 |
14 | )}
15 |
16 |
17 |
Fetch example
18 |
19 | {({ loading, result }) =>
20 | loading ? (
21 | 'Loading...'
22 | ) : (
23 | {result.map(item =>
{item.name}
)}
24 | )
25 | }
26 |
27 |
28 | );
29 | export default App;
30 |
--------------------------------------------------------------------------------
/example/src/components/Fetch.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import With from 'react-with';
5 |
6 | const componentDidMount = ({ setState, url }) => {
7 | setState({ loading: true });
8 |
9 | if (!url) {
10 | return;
11 | }
12 |
13 | fetch(url)
14 | .then(response => response.json())
15 | .then(json =>
16 | setState({
17 | result: json,
18 | loading: false
19 | })
20 | );
21 | };
22 |
23 | const Fetch = ({ url, children, render }) => (
24 |
32 | {children}
33 |
34 | );
35 |
36 | Fetch.propTypes = {
37 | url: PropTypes.string.isRequired,
38 | children: PropTypes.func
39 | };
40 |
41 | export default Fetch;
42 |
--------------------------------------------------------------------------------
/example/src/components/Toggle.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import With from 'react-with';
5 |
6 | const onToggle = ({ state, setState }) =>
7 | setState(state => ({ on: !state.on }));
8 |
9 | const Toggle = ({ children, initial }) => (
10 |
11 | {children}
12 |
13 | );
14 |
15 | Toggle.defaultProps = {
16 | initial: false
17 | };
18 |
19 | Toggle.propTypes = {
20 | children: PropTypes.func,
21 | initial: PropTypes.bool
22 | };
23 |
24 | export default Toggle;
25 |
--------------------------------------------------------------------------------
/example/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 |
4 | import App from './App';
5 |
6 | ReactDOM.render(, document.getElementById('root'));
7 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-with",
3 | "version": "0.2.0",
4 | "description": "⚡️Awesome render props components creation",
5 | "author": "matpaul",
6 | "license": "MIT",
7 | "repository": "matpaul/react-with",
8 | "main": "dist/index.js",
9 | "module": "dist/index.es.js",
10 | "jsnext:main": "dist/index.es.js",
11 | "scripts": {
12 | "precommit": "lint-staged",
13 | "test": "cross-env CI=1 react-scripts test --env=jsdom",
14 | "test:watch": "react-scripts test --env=jsdom",
15 | "build": "rollup -c",
16 | "start": "rollup -c -w",
17 | "prepublish": "yarn run test && yarn run build"
18 | },
19 | "peerDependencies": {
20 | "prop-types": "^15.5.4",
21 | "react": "^15.0.0 || ^16.0.0",
22 | "react-dom": "^15.0.0 || ^16.0.0"
23 | },
24 | "lint-staged": {
25 | "*.{js,json,css,md}": [
26 | "prettier --write",
27 | "git add"
28 | ]
29 | },
30 | "devDependencies": {
31 | "babel-core": "^6.26.0",
32 | "babel-eslint": "^8.2.1",
33 | "babel-plugin-external-helpers": "^6.22.0",
34 | "babel-plugin-transform-runtime": "^6.23.0",
35 | "babel-preset-env": "^1.6.0",
36 | "babel-preset-react": "^6.24.1",
37 | "babel-preset-stage-0": "^6.24.1",
38 | "cross-env": "^5.1.4",
39 | "eslint": "^4.19.1",
40 | "eslint-config-prettier": "^2.9.0",
41 | "eslint-plugin-prettier": "^2.6.0",
42 | "husky": "^0.14.3",
43 | "lint-staged": "^7.2.0",
44 | "react": "^16.2.0",
45 | "react-dom": "^16.2.0",
46 | "react-scripts": "^1.1.1",
47 | "rollup": "^0.54.0",
48 | "rollup-plugin-babel": "^3.0.3",
49 | "rollup-plugin-commonjs": "^8.2.1",
50 | "rollup-plugin-node-resolve": "^3.0.2",
51 | "rollup-plugin-peer-deps-external": "^2.0.0",
52 | "rollup-plugin-postcss": "^1.1.0",
53 | "rollup-plugin-url": "^1.3.0"
54 | },
55 | "files": [
56 | "dist"
57 | ]
58 | }
59 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import babel from 'rollup-plugin-babel';
2 | import commonjs from 'rollup-plugin-commonjs';
3 | import external from 'rollup-plugin-peer-deps-external';
4 | import postcss from 'rollup-plugin-postcss';
5 | import resolve from 'rollup-plugin-node-resolve';
6 | import url from 'rollup-plugin-url';
7 |
8 | import pkg from './package.json';
9 |
10 | export default {
11 | input: 'src/index.js',
12 | output: [
13 | {
14 | file: pkg.main,
15 | format: 'cjs'
16 | },
17 | {
18 | file: pkg.module,
19 | format: 'es'
20 | }
21 | ],
22 | plugins: [
23 | external(),
24 | postcss({
25 | modules: true
26 | }),
27 | url(),
28 | babel({
29 | exclude: 'node_modules/**',
30 | plugins: ['external-helpers']
31 | // externalHelpers: true
32 | }),
33 | resolve(),
34 | commonjs()
35 | ]
36 | };
37 |
--------------------------------------------------------------------------------
/src/__tests__/index.js:
--------------------------------------------------------------------------------
1 | import With from './';
2 |
3 | describe('With', () => {
4 | it('is truthy', () => {
5 | expect(With).toBeTruthy();
6 | });
7 | });
8 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import { isFn } from './utils';
5 |
6 | const { shape, func, object } = PropTypes;
7 |
8 | class With extends React.Component {
9 | static defaultProps = {
10 | state: {},
11 | lifecycle: {}
12 | };
13 |
14 | static propTypes = {
15 | state: object,
16 | render: func,
17 | children: func,
18 | lifecycle: shape({
19 | componentDidMount: func,
20 | componentWillUnmount: func,
21 | shouldComponentUpdate: func,
22 | componentDidUpdate: func,
23 | componentDidCatch: func
24 | })
25 | };
26 |
27 | static hooks = [
28 | 'componentDidMount',
29 | 'componentWillUnmount',
30 | 'shouldComponentUpdate',
31 | 'componentDidUpdate',
32 | 'componentDidCatch'
33 | ];
34 |
35 | set = (...args) => this.setState(...args);
36 |
37 | constructor(props) {
38 | super(props);
39 |
40 | const { state, lifecycle, children, ...other } = props;
41 |
42 | this.state = state;
43 | this.fns = [];
44 |
45 | Object.keys(other)
46 | .map(name => [name, other[name]]) // entries need polyfill
47 | .filter(([name, value]) => isFn(value))
48 | .forEach(([name, fn]) => {
49 | this.fns[name] = (...args) => {
50 | return fn(
51 | { ...props, state: this.state, setState: this.set, ...this.fns },
52 | ...args
53 | );
54 | };
55 | });
56 |
57 | With.hooks.forEach(name => {
58 | const hook = lifecycle[name];
59 |
60 | if (isFn(hook)) {
61 | this[name] = (...args) => {
62 | hook(
63 | { ...other, setState: this.set, state: this.state, ...this.fns },
64 | args
65 | );
66 | };
67 | }
68 | });
69 | }
70 |
71 | render() {
72 | const { children, render } = this.props;
73 |
74 | const args = { ...this.state, ...this.fns };
75 |
76 | const renderTo = isFn(children) ? children : render;
77 |
78 | return renderTo(args);
79 | }
80 | }
81 |
82 | export default With;
83 |
--------------------------------------------------------------------------------
/src/utils.js:
--------------------------------------------------------------------------------
1 | export const isFn = arg => typeof arg === 'function';
2 |
--------------------------------------------------------------------------------