├── .babelrc ├── .eslintrc ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── dist ├── consumer.js ├── index.js └── provider.js ├── package.json └── src ├── __tests__ ├── consumer-test.js └── provider-test.js ├── consumer.js ├── index.js └── provider.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "react"] 3 | } 4 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "rules": { 4 | "strict": 0 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - "5" 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Michele Bertoli 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 | [![Build Status](https://travis-ci.org/MicheleBertoli/react-data-provider.svg?branch=master)](https://travis-ci.org/MicheleBertoli/react-data-provider) 2 | 3 | React Data Provider 4 | =================== 5 | 6 | The [Context](https://facebook.github.io/react/docs/context.html) is a great feature but dealing with it inside your components makes the code less readable and Facebook itself discourages the use of it. 7 | On the other hand, there are some scenarios where you actually need to share data from the root of your app to the rest of the tree. 8 | 9 | That's exactly what this package does for you: 10 | you pass some props to the `Provider`, 11 | you wrap your components into the `Consumer` and they receive all the needed props, transparently, from the Context 12 | (if those props exist, are valid and there are no local props with the same name). 13 | 14 | The benefits of this approach are: 15 | - no need to use the Context 16 | - components are easier to test 17 | - consistency in the way components receive data 18 | 19 | Installation 20 | ------------ 21 | 22 | ```sh 23 | $ npm install react-data-provider --save 24 | ``` 25 | 26 | Usage 27 | ----- 28 | 29 | ```jsx 30 | import React from 'react' 31 | import {render} from 'react-dom' 32 | import {Provider, Consumer} from 'react-data-provider' 33 | 34 | const Button = React.createClass({ 35 | propTypes: { 36 | text: React.PropTypes.string 37 | }, 38 | render() { 39 | return 40 | }, 41 | }) 42 | 43 | const ConsumerButton = Consumer(Button) 44 | 45 | render( 46 | 47 | 48 | , 49 | document.getElementById('root') 50 | ) 51 | ``` 52 | 53 | Test 54 | ---- 55 | 56 | ```sh 57 | $ npm test 58 | ``` 59 | -------------------------------------------------------------------------------- /dist/consumer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; 8 | 9 | var _react = require('react'); 10 | 11 | var _react2 = _interopRequireDefault(_react); 12 | 13 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 14 | 15 | exports.default = function (Component) { 16 | 17 | return _react2.default.createClass({ 18 | 19 | contextTypes: { 20 | __data: _react2.default.PropTypes.object 21 | }, 22 | 23 | propExists: function propExists(prop) { 24 | return this.props[prop]; 25 | }, 26 | contextIsValid: function contextIsValid(prop) { 27 | return !Component.propTypes[prop](this.context.__data, prop); 28 | }, 29 | contextToProps: function contextToProps() { 30 | var newProps = {}; 31 | for (var prop in Component.propTypes) { 32 | if (!this.propExists(prop) && this.contextIsValid(prop)) { 33 | newProps[prop] = this.context.__data[prop]; 34 | } 35 | } 36 | return newProps; 37 | }, 38 | render: function render() { 39 | return _react2.default.createElement(Component, _extends({}, this.contextToProps(), this.props)); 40 | } 41 | }); 42 | }; -------------------------------------------------------------------------------- /dist/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.Consumer = exports.Provider = undefined; 7 | 8 | var _provider = require('./provider.js'); 9 | 10 | var _provider2 = _interopRequireDefault(_provider); 11 | 12 | var _consumer = require('./consumer.js'); 13 | 14 | var _consumer2 = _interopRequireDefault(_consumer); 15 | 16 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 17 | 18 | exports.Provider = _provider2.default; 19 | exports.Consumer = _consumer2.default; -------------------------------------------------------------------------------- /dist/provider.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _react = require('react'); 8 | 9 | var _react2 = _interopRequireDefault(_react); 10 | 11 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 12 | 13 | exports.default = _react2.default.createClass({ 14 | displayName: 'provider', 15 | 16 | 17 | childContextTypes: { 18 | __data: _react2.default.PropTypes.object 19 | }, 20 | 21 | getChildContext: function getChildContext() { 22 | return { 23 | __data: this.props 24 | }; 25 | }, 26 | 27 | render: function render() { 28 | return this.props.children; 29 | } 30 | }); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-data-provider", 3 | "version": "1.0.1", 4 | "description": "Because the Context is too mainstream", 5 | "main": "dist/index.js", 6 | "scripts": { 7 | "test": "jest", 8 | "build": "babel src -d dist --ignore __tests__" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/MicheleBertoli/react-data-provider.git" 13 | }, 14 | "keywords": [ 15 | "react", 16 | "react-component", 17 | "context" 18 | ], 19 | "author": "Michele Bertoli", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/MicheleBertoli/react-data-provider/issues" 23 | }, 24 | "homepage": "https://github.com/MicheleBertoli/react-data-provider#readme", 25 | "devDependencies": { 26 | "babel-cli": "^6.5.1", 27 | "babel-eslint": "^5.0.0", 28 | "babel-jest": "^6.0.1", 29 | "babel-preset-es2015": "^6.5.0", 30 | "babel-preset-react": "^6.5.0", 31 | "jest-cli": "^0.8.2", 32 | "react": "^0.14.7", 33 | "react-addons-test-utils": "^0.14.7", 34 | "react-dom": "^0.14.7" 35 | }, 36 | "jest": { 37 | "scriptPreprocessor": "/node_modules/babel-jest", 38 | "unmockedModulePathPatterns": [ 39 | "/node_modules/react", 40 | "/node_modules/react-dom", 41 | "/node_modules/react-addons-test-utils", 42 | "/node_modules/fbjs" 43 | ] 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/__tests__/consumer-test.js: -------------------------------------------------------------------------------- 1 | jest.dontMock('../provider') 2 | jest.dontMock('../consumer') 3 | 4 | import React from 'react' 5 | import TestUtils from 'react-addons-test-utils' 6 | 7 | const Provider = require('../provider').default 8 | const Consumer = require('../consumer').default 9 | 10 | describe('Consumer', () => { 11 | 12 | const Child = React.createClass({ 13 | propTypes: { 14 | foo: React.PropTypes.string, 15 | }, 16 | render() { 17 | return null 18 | }, 19 | }) 20 | const ConsumerChild = Consumer(Child) 21 | 22 | it('transforms the context to props for the specified types', () => { 23 | 24 | const tree = TestUtils.renderIntoDocument( 25 | 26 | 27 | 28 | ) 29 | const child = TestUtils.findRenderedComponentWithType(tree, Child) 30 | 31 | expect(child.props.foo).toBe('bar') 32 | 33 | }) 34 | 35 | it('transforms the context to props for the specified types only', () => { 36 | 37 | const tree = TestUtils.renderIntoDocument( 38 | 39 | 40 | 41 | ) 42 | const child = TestUtils.findRenderedComponentWithType(tree, Child) 43 | 44 | expect(child.props.yo).toBeUndefined() 45 | 46 | }) 47 | 48 | it('transforms the context to props only if they match the type', () => { 49 | 50 | const tree = TestUtils.renderIntoDocument( 51 | 52 | 53 | 54 | ) 55 | const child = TestUtils.findRenderedComponentWithType(tree, Child) 56 | 57 | expect(child.props.foo).toBeUndefined() 58 | 59 | }) 60 | 61 | it('transforms the context to props only if there are no local props with the same name', () => { 62 | 63 | const tree = TestUtils.renderIntoDocument( 64 | 65 | 66 | 67 | ) 68 | const child = TestUtils.findRenderedComponentWithType(tree, Child) 69 | 70 | expect(child.props.foo).toBe('yo') 71 | 72 | }) 73 | 74 | }) 75 | -------------------------------------------------------------------------------- /src/__tests__/provider-test.js: -------------------------------------------------------------------------------- 1 | jest.dontMock('../provider') 2 | 3 | import React from 'react' 4 | import TestUtils from 'react-addons-test-utils' 5 | 6 | const Provider = require('../provider').default 7 | 8 | describe('Provider', () => { 9 | 10 | const Child = React.createClass({ 11 | contextTypes: { 12 | __data: React.PropTypes.object, 13 | }, 14 | render() { 15 | return null 16 | }, 17 | }) 18 | 19 | it('creates a data context using the received props', () => { 20 | 21 | const tree = TestUtils.renderIntoDocument( 22 | 23 | 24 | 25 | ) 26 | const child = TestUtils.findRenderedComponentWithType(tree, Child) 27 | 28 | expect(child.context.__data.foo).toBe('bar') 29 | 30 | }) 31 | 32 | }) 33 | -------------------------------------------------------------------------------- /src/consumer.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default Component => { 4 | 5 | return React.createClass({ 6 | 7 | contextTypes: { 8 | __data: React.PropTypes.object, 9 | }, 10 | 11 | propExists(prop) { 12 | return this.props[prop] 13 | }, 14 | 15 | contextIsValid(prop) { 16 | return !Component.propTypes[prop](this.context.__data, prop) 17 | }, 18 | 19 | contextToProps() { 20 | const newProps = {} 21 | for (let prop in Component.propTypes) { 22 | if (!this.propExists(prop) && this.contextIsValid(prop)) { 23 | newProps[prop] = this.context.__data[prop] 24 | } 25 | } 26 | return newProps 27 | }, 28 | 29 | render() { 30 | return 31 | }, 32 | 33 | }) 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import Provider from './provider.js' 2 | import Consumer from './consumer.js' 3 | 4 | export { 5 | Provider, 6 | Consumer, 7 | } 8 | -------------------------------------------------------------------------------- /src/provider.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default React.createClass({ 4 | 5 | childContextTypes: { 6 | __data: React.PropTypes.object, 7 | }, 8 | 9 | getChildContext: function() { 10 | return { 11 | __data: this.props, 12 | } 13 | }, 14 | 15 | render() { 16 | return this.props.children 17 | }, 18 | 19 | }) 20 | --------------------------------------------------------------------------------