├── .babelrc ├── .eslintignore ├── .npmignore ├── test ├── babel │ └── index.js └── connect-to-stores-test.js ├── .gitignore ├── .travis.yml ├── .editorconfig ├── dist.config.js ├── dist.min.config.js ├── bower.json ├── dist ├── connectToStores.min.js └── connectToStores.js ├── src └── connectToStores.js ├── package.json ├── README.md └── .eslintrc /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "stage": 0 3 | } -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | src/utils/TimeTravel.js 2 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | TODO 2 | bower.json 3 | coverage 4 | examples 5 | npm-debug.log 6 | src 7 | test/browser/tests.js 8 | web 9 | -------------------------------------------------------------------------------- /test/babel/index.js: -------------------------------------------------------------------------------- 1 | require('babel-core/external-helpers') 2 | require('babel/register-without-polyfill')({ 3 | stage: 0 4 | }) 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | TODO 2 | coverage 3 | node_modules 4 | npm-debug.log 5 | test/browser/tests.js 6 | lib 7 | utils/* 8 | flux.js 9 | flux-build.js 10 | flux-build.min.js 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | script: npm run lint && npm run coverage 3 | after_script: cat ./coverage/lcov.info | coveralls 4 | node_js: 5 | - "0.10" 6 | - "0.12" 7 | - iojs-v1.8.1 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | -------------------------------------------------------------------------------- /dist.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | context: __dirname + '/src', 3 | entry: { 4 | 'connectToStores': ['./connectToStores.js'] 5 | }, 6 | output: { 7 | path: __dirname + '/dist', 8 | filename: '[name].js', 9 | library: 'Alt', 10 | libraryTarget: 'umd' 11 | }, 12 | module: { 13 | loaders: [{ 14 | test: /\.js$/, 15 | loader: 'babel', 16 | exclude: /node_modules/ 17 | }] 18 | }, 19 | externals: { 20 | 'react': 'react', 21 | 'react/addons': 'react/addons', 22 | 'alt': 'alt' 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /dist.min.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | context: __dirname + '/src', 3 | entry: { 4 | 'connectToStores': ['./connectToStores.js'] 5 | }, 6 | output: { 7 | path: __dirname + '/dist', 8 | filename: '[name].min.js', 9 | library: 'Alt', 10 | libraryTarget: 'umd' 11 | }, 12 | module: { 13 | loaders: [{ 14 | test: /\.js$/, 15 | loader: 'babel', 16 | exclude: /node_modules/ 17 | }] 18 | }, 19 | externals: { 20 | 'react': 'react', 21 | 'react/addons': 'react/addons', 22 | 'alt': 'alt' 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "alt-connect-to-stores", 3 | "version": "0.0.1", 4 | "homepage": "https://github.com/altjs/connect-to-stores", 5 | "authors": [ 6 | "Josh Perez ", 7 | "Brian Link " 8 | ], 9 | "description": "", 10 | "main": "dist/connect-to-stores.js", 11 | "devDependencies": { 12 | "babel": "^4.0.1", 13 | "coveralls": "^2.11.2", 14 | "istanbul": "^0.3.5", 15 | "mocha": "^2.1.0", 16 | "alt": "^0.17.1" 17 | }, 18 | "keywords": [ 19 | "alt", 20 | "es6", 21 | "flow", 22 | "flux", 23 | "react", 24 | "unidirectional" 25 | ], 26 | "license": "MIT", 27 | "ignore": [ 28 | "**/.*", 29 | "node_modules", 30 | "bower_components", 31 | "test", 32 | "tests", 33 | "examples", 34 | "src", 35 | "coverage-test.js", 36 | "coverage", 37 | "TODO" 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /dist/connectToStores.min.js: -------------------------------------------------------------------------------- 1 | !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e(require("react")):"function"==typeof define&&define.amd?define(["react"],e):"object"==typeof exports?exports.Alt=e(require("react")):t.Alt=e(t.react)}(this,function(t){return function(t){function e(n){if(o[n])return o[n].exports;var r=o[n]={exports:{},id:n,loaded:!1};return t[n].call(r.exports,r,r.exports,e),r.loaded=!0,r.exports}var o={};return e.m=t,e.c=o,e.p="",e(0)}([function(t,e,o){t.exports=o(1)},function(t,e,o){"use strict";function n(t){return t&&t.__esModule?t:{"default":t}}function r(t){var e=void 0===arguments[1]?t:arguments[1];return function(){if(!c(t.getStores))throw new Error("connectToStores() expects the wrapped component to have a static getStores() method");if(!c(t.getPropsFromStores))throw new Error("connectToStores() expects the wrapped component to have a static getPropsFromStores() method");var o=i["default"].createClass({displayName:"StoreConnection",getInitialState:function(){return t.getPropsFromStores(this.props,this.context)},componentWillReceiveProps:function(e){this.setState(t.getPropsFromStores(e,this.context))},componentDidMount:function(){var e=this,o=t.getStores(this.props,this.context);this.storeListeners=o.map(function(t){return t.listen(e.onChange)}),t.componentDidConnect&&t.componentDidConnect(this.props,this.context)},componentWillUnmount:function(){this.storeListeners.forEach(function(t){return t()})},onChange:function(){this.setState(t.getPropsFromStores(this.props,this.context))},render:function(){return i["default"].createElement(e,a({},this.props,this.state))}});return o}()}Object.defineProperty(e,"__esModule",{value:!0});var s=o(2),i=n(s),c=function(t){return"function"==typeof t},u=function(t,e){e.forEach(function(e){Object.keys(Object(e)).forEach(function(o){t(o,e[o])})})},a=function(t){for(var e=arguments.length,o=Array(e>1?e-1:0),n=1;e>n;n++)o[n-1]=arguments[n];return u(function(e,o){return t[e]=o},o),t};e["default"]=r,t.exports=e["default"]},function(e,o){e.exports=t}])}); -------------------------------------------------------------------------------- /src/connectToStores.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | // @todo Where to get these from? 4 | const isFunction = x => typeof x === 'function' 5 | const eachObject = (f, o) => { 6 | o.forEach((from) => { 7 | Object.keys(Object(from)).forEach((key) => { 8 | f(key, from[key]) 9 | }) 10 | }) 11 | } 12 | const assign = (target, ...source) => { 13 | eachObject((key, value) => target[key] = value, source) 14 | return target 15 | } 16 | 17 | function connectToStores(Spec, Component = Spec) { 18 | // Check for required static methods. 19 | if (!isFunction(Spec.getStores)) { 20 | throw new Error('connectToStores() expects the wrapped component to have a static getStores() method') 21 | } 22 | if (!isFunction(Spec.getPropsFromStores)) { 23 | throw new Error('connectToStores() expects the wrapped component to have a static getPropsFromStores() method') 24 | } 25 | 26 | const StoreConnection = React.createClass({ 27 | getInitialState() { 28 | return Spec.getPropsFromStores(this.props, this.context) 29 | }, 30 | 31 | componentWillReceiveProps(nextProps) { 32 | this.setState(Spec.getPropsFromStores(nextProps, this.context)) 33 | }, 34 | 35 | componentDidMount() { 36 | const stores = Spec.getStores(this.props, this.context) 37 | this.storeListeners = stores.map((store) => { 38 | return store.listen(this.onChange) 39 | }) 40 | if (Spec.componentDidConnect) { 41 | Spec.componentDidConnect(this.props, this.context) 42 | } 43 | }, 44 | 45 | componentWillUnmount() { 46 | this.storeListeners.forEach(unlisten => unlisten()) 47 | }, 48 | 49 | onChange() { 50 | this.setState(Spec.getPropsFromStores(this.props, this.context)) 51 | }, 52 | 53 | render() { 54 | return React.createElement( 55 | Component, 56 | assign({}, this.props, this.state) 57 | ) 58 | } 59 | }) 60 | 61 | return StoreConnection 62 | } 63 | 64 | export default connectToStores 65 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@alt/connect-to-stores", 3 | "version": "0.0.1", 4 | "description": "", 5 | "main": "lib/connectToStores", 6 | "dependencies": {}, 7 | "devDependencies": { 8 | "alt": "0.17.1", 9 | "babel": "5.6.14", 10 | "babel-core": "5.6.15", 11 | "babel-eslint": "3.1.18", 12 | "babel-loader": "^5.1.4", 13 | "babelify": "6.1.2", 14 | "browserify": "^10.2.6", 15 | "chai": "^2.3.0", 16 | "coveralls": "^2.11.2", 17 | "envify": "^3.4.0", 18 | "es6-promise": "^2.1.1", 19 | "eslint": "^0.24.0", 20 | "eslint-plugin-react": "2.5.2", 21 | "istanbul": "^0.3.14", 22 | "jsdom": "^3.1.2", 23 | "lunr": "^0.5.9", 24 | "mocha": "^2.2.4", 25 | "object-assign": "^2.0.0", 26 | "react": "^0.13.3", 27 | "rimraf": "^2.3.2", 28 | "sinon": "^1.14.0", 29 | "webpack": "^1.9.12" 30 | }, 31 | "repository": { 32 | "type": "git", 33 | "url": "https://github.com/altjs/connect-to-stores.git" 34 | }, 35 | "authors": [ 36 | "Josh Perez ", 37 | "Jonathan Lehman ", 38 | "Brian Link " 39 | ], 40 | "license": "MIT", 41 | "scripts": { 42 | "build": "npm run clean && npm run transpile && npm run build-browser", 43 | "build-browser": "./node_modules/.bin/webpack --config dist.config.js && ./node_modules/.bin/webpack -p --config dist.min.config.js", 44 | "coverage": "npm run transpile-cover && ./node_modules/.bin/istanbul cover node_modules/mocha/bin/_mocha -- -u exports -R tap --require ./test/babel test", 45 | "clean": "./node_modules/.bin/rimraf lib", 46 | "lint": "./node_modules/.bin/eslint src", 47 | "prepublish": "npm run lint && npm run build", 48 | "pretest": "npm run clean && npm run transpile", 49 | "test": "npm run test-node", 50 | "test-browser": "./node_modules/.bin/browserify test/browser/index.js -t babelify --outfile test/browser/tests.js", 51 | "test-node": "./node_modules/.bin/mocha -u exports -R nyan --require ./test/babel test", 52 | "transpile": "./node_modules/.bin/babel src --out-dir lib", 53 | "transpile-cover": "./node_modules/.bin/babel src --out-dir lib -r" 54 | }, 55 | "keywords": [ 56 | "alt", 57 | "es6", 58 | "flow", 59 | "flux", 60 | "react", 61 | "unidirectional" 62 | ], 63 | "config": { 64 | "ghooks": { 65 | "pre-push": "npm run lint" 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # connectToStores (for alt) 2 | 3 | 'Higher Order Component' for alt flux that controls the props of a wrapped 4 | component via stores. 5 | 6 | **Alt** is an Isomorphic flux implementation. 7 | 8 | Check out the [API Reference](http://alt.js.org/docs/) for full in-depth alt docs. For a high-level walk-through on flux, take a look at the [Getting Started](http://alt.js.org/guide/) guide. 9 | 10 | [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/goatslacker/alt?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 11 | 12 | ``` 13 | // @todo Use real badges for this package. 14 | /* 15 | [![NPM version](https://badge.fury.io/js/alt.svg)](http://badge.fury.io/js/alt) 16 | [![Build Status](https://secure.travis-ci.org/goatslacker/alt.svg?branch=master)](http://travis-ci.org/goatslacker/alt) 17 | [![Coverage Status](https://img.shields.io/coveralls/goatslacker/alt.svg?style=flat)](https://coveralls.io/r/goatslacker/alt) 18 | [![Dependency Status](https://david-dm.org/goatslacker/alt.svg)](https://david-dm.org/goatslacker/alt) 19 | [![Download Count](https://img.shields.io/npm/dm/alt.svg?style=flat)](https://www.npmjs.com/package/alt) 20 | [![JS.ORG](https://img.shields.io/badge/js.org-alt-ffb400.svg?style=flat-square)](http://js.org) 21 | */ 22 | ``` 23 | 24 | ## How to use 25 | 26 | Expects the Component to have two static methods: 27 | - getStores(): Should return an array of stores. 28 | - getPropsFromStores(props): Should return the props from the stores. 29 | 30 | **Using old React.createClass() style:** 31 | 32 | ```js 33 | const MyComponent = React.createClass({ 34 | statics: { 35 | getStores(props) { 36 | return [myStore] 37 | }, 38 | getPropsFromStores(props) { 39 | return myStore.getState() 40 | } 41 | }, 42 | render() { 43 | // Use this.props like normal ... 44 | } 45 | }) 46 | MyComponent = connectToStores(MyComponent) 47 | ``` 48 | 49 | **Using ES6 Class:** 50 | 51 | ```js 52 | class MyComponent extends React.Component { 53 | static getStores(props) { 54 | return [myStore] 55 | } 56 | static getPropsFromStores(props) { 57 | return myStore.getState() 58 | } 59 | render() { 60 | // Use this.props like normal ... 61 | } 62 | } 63 | MyComponent = connectToStores(MyComponent) 64 | ``` 65 | 66 | **Using ES7 Decorators (proposal, stage 0):** 67 | 68 | ```js 69 | @connectToStores 70 | class MyComponent extends React.Component { 71 | static getStores(props) { 72 | return [myStore] 73 | } 74 | static getPropsFromStores(props) { 75 | return myStore.getState() 76 | } 77 | render() { 78 | // Use this.props like normal ... 79 | } 80 | } 81 | ``` 82 | 83 | A great explanation of the merits of higher order components can be found at 84 | http://bit.ly/1abPkrP 85 | 86 | 87 | ## License 88 | 89 | [![MIT](https://img.shields.io/npm/l/alt.svg?style=flat)](http://josh.mit-license.org) 90 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "node": true, 4 | "browser": true, 5 | "es6": true 6 | }, 7 | "parser": "babel-eslint", 8 | "ecmaFeatures": { 9 | "modules": true, 10 | "jsx": true 11 | }, 12 | "plugins": [ 13 | "react" 14 | ], 15 | "rules": { 16 | /* airbnb javascript style guide rules */ 17 | "strict": [2, "never"], 18 | // es6 19 | "no-var": 0, 20 | // variables 21 | "no-shadow": 2, 22 | "no-shadow-restricted-names": 2, 23 | "no-unused-vars": [2, { 24 | "vars": "local", 25 | "args": "after-used" 26 | }], 27 | "no-use-before-define": 2, 28 | // possible errors 29 | "comma-dangle": [0, "always"], 30 | "no-cond-assign": [2, "always"], 31 | "no-debugger": 1, 32 | "no-alert": 1, 33 | "no-constant-condition": 1, 34 | "no-dupe-keys": 2, 35 | "no-duplicate-case": 2, 36 | "no-empty": 2, 37 | "no-ex-assign": 2, 38 | "no-extra-boolean-cast": 0, 39 | "no-extra-semi": 2, 40 | "no-func-assign": 2, 41 | "no-inner-declarations": 2, 42 | "no-invalid-regexp": 2, 43 | "no-irregular-whitespace": 2, 44 | "no-obj-calls": 2, 45 | "no-reserved-keys": 2, 46 | "no-sparse-arrays": 2, 47 | "no-unreachable": 2, 48 | "use-isnan": 2, 49 | "block-scoped-var": 2, 50 | // best practices 51 | "consistent-return": 2, 52 | "curly": [2, "multi-line"], 53 | "default-case": 2, 54 | "dot-notation": [2, { 55 | "allowKeywords": true 56 | }], 57 | "eqeqeq": 2, 58 | "guard-for-in": 2, 59 | "no-caller": 2, 60 | "no-eq-null": 0, 61 | "no-eval": 2, 62 | "no-extend-native": 2, 63 | "no-extra-bind": 2, 64 | "no-fallthrough": 2, 65 | "no-floating-decimal": 2, 66 | "no-implied-eval": 2, 67 | "no-lone-blocks": 2, 68 | "no-loop-func": 2, 69 | "no-multi-str": 2, 70 | "no-native-reassign": 2, 71 | "no-new": 2, 72 | "no-new-func": 2, 73 | "no-new-wrappers": 2, 74 | "no-octal": 2, 75 | "no-octal-escape": 2, 76 | "no-param-reassign": 2, 77 | "no-proto": 2, 78 | "no-redeclare": 2, 79 | "no-return-assign": 2, 80 | "no-script-url": 2, 81 | "no-self-compare": 2, 82 | "no-sequences": 2, 83 | "no-throw-literal": 2, 84 | "no-with": 2, 85 | "radix": 2, 86 | "vars-on-top": 2, 87 | "wrap-iife": [2, "any"], 88 | "yoda": 2, 89 | // style 90 | "indent": [2, 2], 91 | "brace-style": [2, 92 | "1tbs", { 93 | "allowSingleLine": true 94 | }], 95 | "quotes": [ 96 | 2, "single", "avoid-escape" 97 | ], 98 | "camelcase": [2, { 99 | "properties": "never" 100 | }], 101 | "comma-spacing": [2, { 102 | "before": false, 103 | "after": true 104 | }], 105 | "comma-style": [2, "last"], 106 | "eol-last": 2, 107 | "key-spacing": [2, { 108 | "beforeColon": false, 109 | "afterColon": true 110 | }], 111 | "new-cap": [2, { 112 | "newIsCap": true 113 | }], 114 | "no-multiple-empty-lines": [2, { 115 | "max": 2 116 | }], 117 | "no-nested-ternary": 2, 118 | "no-new-object": 2, 119 | "no-spaced-func": 2, 120 | "no-trailing-spaces": 2, 121 | "no-wrap-func": 2, 122 | "no-underscore-dangle": 0, 123 | "one-var": [2, "never"], 124 | "padded-blocks": [2, "never"], 125 | "semi-spacing": [2, { 126 | "before": false, 127 | "after": true 128 | }], 129 | "space-after-keywords": 2, 130 | "space-before-blocks": 2, 131 | "space-infix-ops": 2, 132 | "space-return-throw-case": 2, 133 | "spaced-line-comment": 2, 134 | 135 | /* custom rules */ 136 | "no-console": 0, 137 | "space-before-function-paren": [2, { 138 | "named": "never", 139 | "anonymous": "always" 140 | }], 141 | "semi": [2, "never"], 142 | "func-names": 0, 143 | "consistent-this": [2, "this"], 144 | "func-style": [0, "expression"], 145 | "generator-star": [2, "end"], 146 | "max-nested-callbacks": [2, 3], 147 | "new-parens": 2, 148 | "no-array-constructor": 2, 149 | "no-else-return": 0, 150 | "no-inline-comments": 2, 151 | "no-lonely-if": 2, 152 | "no-mixed-spaces-and-tabs": 2, 153 | "no-multiple-empty-lines": [1, { 154 | "max": 2 155 | }], 156 | "no-ternary": 0, 157 | "operator-assignment": 0, 158 | "quote-props": [2, "as-needed"], 159 | "sort-vars": [0, { 160 | "ignoreCase": true 161 | }], 162 | "space-in-brackets": [0, "never", 163 | { 164 | "arraysInArrays": false, 165 | "arraysInObjects": false, 166 | "singleValue": false, 167 | "objectsInArrays": false, 168 | "objectsInObjects": false, 169 | "propertyName": false 170 | } 171 | ], 172 | "space-in-parens": [2, "never"], 173 | "space-unary-ops": [2, 174 | { 175 | "words": true, 176 | "nonwords": false 177 | } 178 | ], 179 | "wrap-regex": 2, 180 | 181 | /* react rules */ 182 | "react/jsx-uses-vars": 1 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /dist/connectToStores.js: -------------------------------------------------------------------------------- 1 | (function webpackUniversalModuleDefinition(root, factory) { 2 | if(typeof exports === 'object' && typeof module === 'object') 3 | module.exports = factory(require("react")); 4 | else if(typeof define === 'function' && define.amd) 5 | define(["react"], factory); 6 | else if(typeof exports === 'object') 7 | exports["Alt"] = factory(require("react")); 8 | else 9 | root["Alt"] = factory(root["react"]); 10 | })(this, function(__WEBPACK_EXTERNAL_MODULE_2__) { 11 | return /******/ (function(modules) { // webpackBootstrap 12 | /******/ // The module cache 13 | /******/ var installedModules = {}; 14 | 15 | /******/ // The require function 16 | /******/ function __webpack_require__(moduleId) { 17 | 18 | /******/ // Check if module is in cache 19 | /******/ if(installedModules[moduleId]) 20 | /******/ return installedModules[moduleId].exports; 21 | 22 | /******/ // Create a new module (and put it into the cache) 23 | /******/ var module = installedModules[moduleId] = { 24 | /******/ exports: {}, 25 | /******/ id: moduleId, 26 | /******/ loaded: false 27 | /******/ }; 28 | 29 | /******/ // Execute the module function 30 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 31 | 32 | /******/ // Flag the module as loaded 33 | /******/ module.loaded = true; 34 | 35 | /******/ // Return the exports of the module 36 | /******/ return module.exports; 37 | /******/ } 38 | 39 | 40 | /******/ // expose the modules object (__webpack_modules__) 41 | /******/ __webpack_require__.m = modules; 42 | 43 | /******/ // expose the module cache 44 | /******/ __webpack_require__.c = installedModules; 45 | 46 | /******/ // __webpack_public_path__ 47 | /******/ __webpack_require__.p = ""; 48 | 49 | /******/ // Load entry module and return exports 50 | /******/ return __webpack_require__(0); 51 | /******/ }) 52 | /************************************************************************/ 53 | /******/ ([ 54 | /* 0 */ 55 | /***/ function(module, exports, __webpack_require__) { 56 | 57 | module.exports = __webpack_require__(1); 58 | 59 | 60 | /***/ }, 61 | /* 1 */ 62 | /***/ function(module, exports, __webpack_require__) { 63 | 64 | 'use strict'; 65 | 66 | Object.defineProperty(exports, '__esModule', { 67 | value: true 68 | }); 69 | 70 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 71 | 72 | var _react = __webpack_require__(2); 73 | 74 | var _react2 = _interopRequireDefault(_react); 75 | 76 | // @todo Where to get these from? 77 | var isFunction = function isFunction(x) { 78 | return typeof x === 'function'; 79 | }; 80 | var eachObject = function eachObject(f, o) { 81 | o.forEach(function (from) { 82 | Object.keys(Object(from)).forEach(function (key) { 83 | f(key, from[key]); 84 | }); 85 | }); 86 | }; 87 | var assign = function assign(target) { 88 | for (var _len = arguments.length, source = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { 89 | source[_key - 1] = arguments[_key]; 90 | } 91 | 92 | eachObject(function (key, value) { 93 | return target[key] = value; 94 | }, source); 95 | return target; 96 | }; 97 | 98 | function connectToStores(Spec) { 99 | var Component = arguments[1] === undefined ? Spec : arguments[1]; 100 | return (function () { 101 | // Check for required static methods. 102 | if (!isFunction(Spec.getStores)) { 103 | throw new Error('connectToStores() expects the wrapped component to have a static getStores() method'); 104 | } 105 | if (!isFunction(Spec.getPropsFromStores)) { 106 | throw new Error('connectToStores() expects the wrapped component to have a static getPropsFromStores() method'); 107 | } 108 | 109 | var StoreConnection = _react2['default'].createClass({ 110 | displayName: 'StoreConnection', 111 | 112 | getInitialState: function getInitialState() { 113 | return Spec.getPropsFromStores(this.props, this.context); 114 | }, 115 | 116 | componentWillReceiveProps: function componentWillReceiveProps(nextProps) { 117 | this.setState(Spec.getPropsFromStores(nextProps, this.context)); 118 | }, 119 | 120 | componentDidMount: function componentDidMount() { 121 | var _this = this; 122 | 123 | var stores = Spec.getStores(this.props, this.context); 124 | this.storeListeners = stores.map(function (store) { 125 | return store.listen(_this.onChange); 126 | }); 127 | if (Spec.componentDidConnect) { 128 | Spec.componentDidConnect(this.props, this.context); 129 | } 130 | }, 131 | 132 | componentWillUnmount: function componentWillUnmount() { 133 | this.storeListeners.forEach(function (unlisten) { 134 | return unlisten(); 135 | }); 136 | }, 137 | 138 | onChange: function onChange() { 139 | this.setState(Spec.getPropsFromStores(this.props, this.context)); 140 | }, 141 | 142 | render: function render() { 143 | return _react2['default'].createElement(Component, assign({}, this.props, this.state)); 144 | } 145 | }); 146 | 147 | return StoreConnection; 148 | })(); 149 | } 150 | 151 | exports['default'] = connectToStores; 152 | module.exports = exports['default']; 153 | 154 | /***/ }, 155 | /* 2 */ 156 | /***/ function(module, exports) { 157 | 158 | module.exports = __WEBPACK_EXTERNAL_MODULE_2__; 159 | 160 | /***/ } 161 | /******/ ]) 162 | }); 163 | ; -------------------------------------------------------------------------------- /test/connect-to-stores-test.js: -------------------------------------------------------------------------------- 1 | import { jsdom } from 'jsdom' 2 | import Alt from 'alt' 3 | import React from 'react/addons' 4 | import connectToStores from '../' 5 | import { assert } from 'chai' 6 | import sinon from 'sinon' 7 | 8 | const { TestUtils } = React.addons 9 | 10 | const alt = new Alt() 11 | 12 | const testActions = alt.generateActions('updateFoo') 13 | 14 | const testStore = alt.createStore( 15 | class TestStore { 16 | constructor() { 17 | this.bindAction(testActions.updateFoo, this.onChangeFoo) 18 | this.foo = 'Bar' 19 | } 20 | onChangeFoo(newValue) { 21 | this.foo = newValue 22 | } 23 | } 24 | ) 25 | 26 | export default { 27 | 'connectToStores wrapper': { 28 | beforeEach() { 29 | global.document = jsdom('') 30 | global.window = global.document.parentWindow 31 | global.navigator = global.window.navigator 32 | require('react/lib/ExecutionEnvironment').canUseDOM = true 33 | 34 | alt.recycle() 35 | }, 36 | 37 | afterEach() { 38 | delete global.document 39 | delete global.window 40 | delete global.navigator 41 | }, 42 | 43 | 'resolve props on re-render'() { 44 | const FooStore = alt.createStore(function () { 45 | this.x = 1 46 | }, 'FooStore') 47 | 48 | const getPropsFromStores = sinon.stub().returns(FooStore.getState()) 49 | 50 | const Child = connectToStores(React.createClass({ 51 | statics: { 52 | getStores(props) { 53 | return [FooStore] 54 | }, 55 | 56 | getPropsFromStores 57 | }, 58 | render() { 59 | return {this.props.x + this.props.y} 60 | } 61 | })) 62 | 63 | const Parent = React.createClass({ 64 | getInitialState() { 65 | return { y: 0 } 66 | }, 67 | componentDidMount() { 68 | this.setState({ y: 1 }) 69 | }, 70 | render() { 71 | return 72 | } 73 | }) 74 | 75 | const node = TestUtils.renderIntoDocument( 76 | 77 | ) 78 | 79 | assert(getPropsFromStores.callCount === 2, 'getPropsFromStores called twice') 80 | 81 | const span = TestUtils.findRenderedDOMComponentWithTag(node, 'span') 82 | assert(span.getDOMNode().innerHTML === '2', 'prop passed in is correct') 83 | }, 84 | 85 | 'missing the static getStores() method should throw'() { 86 | const BadComponentOne = React.createClass({ 87 | render() { 88 | return React.createElement('div', null, 'Bad') 89 | } 90 | }) 91 | 92 | assert.throws(() => connectToStores(BadComponentOne), 'expects the wrapped component to have a static getStores() method') 93 | }, 94 | 95 | 'element mounts and unmounts'() { 96 | const div = document.createElement('div') 97 | 98 | const LegacyComponent = connectToStores(React.createClass({ 99 | statics: { 100 | getStores() { 101 | return [testStore] 102 | }, 103 | getPropsFromStores(props) { 104 | return testStore.getState() 105 | } 106 | }, 107 | render() { 108 | return React.createElement('div', null, `Foo${this.props.delim}${this.props.foo}`) 109 | } 110 | })) 111 | 112 | React.render( 113 | 114 | , div) 115 | 116 | React.unmountComponentAtNode(div) 117 | }, 118 | 119 | 'missing the static getPropsFromStores() method should throw'() { 120 | const BadComponentTwo = React.createClass({ 121 | statics: { 122 | getStores() { 123 | return [testStore] 124 | } 125 | }, 126 | render() { 127 | return React.createElement('div', null, 'Bad') 128 | } 129 | }) 130 | 131 | assert.throws(() => connectToStores(BadComponentTwo), 'expects the wrapped component to have a static getPropsFromStores() method') 132 | }, 133 | 134 | 'createClass() component can get props from stores'() { 135 | const LegacyComponent = React.createClass({ 136 | statics: { 137 | getStores() { 138 | return [testStore] 139 | }, 140 | getPropsFromStores(props) { 141 | return testStore.getState() 142 | } 143 | }, 144 | render() { 145 | return React.createElement('div', null, `Foo${this.props.delim}${this.props.foo}`) 146 | } 147 | }) 148 | 149 | const WrappedComponent = connectToStores(LegacyComponent) 150 | const element = React.createElement(WrappedComponent, {delim: ': '}) 151 | const output = React.renderToStaticMarkup(element) 152 | assert.include(output, 'Foo: Bar') 153 | }, 154 | 155 | 'component can get use stores from props'() { 156 | const LegacyComponent = React.createClass({ 157 | statics: { 158 | getStores(props) { 159 | return [props.store] 160 | }, 161 | getPropsFromStores(props) { 162 | return props.store.getState() 163 | } 164 | }, 165 | render() { 166 | return React.createElement('div', null, `Foo${this.props.delim}${this.props.foo}`) 167 | } 168 | }) 169 | 170 | const WrappedComponent = connectToStores(LegacyComponent) 171 | const element = React.createElement(WrappedComponent, {delim: ': ', store: testStore}) 172 | const output = React.renderToStaticMarkup(element) 173 | assert.include(output, 'Foo: Bar') 174 | }, 175 | 176 | 'ES6 class component responds to store events'() { 177 | class ClassComponent extends React.Component { 178 | render() { 179 | return 180 | } 181 | } 182 | 183 | const WrappedComponent = connectToStores({ 184 | getStores() { 185 | return [testStore] 186 | }, 187 | getPropsFromStores(props) { 188 | return testStore.getState() 189 | } 190 | }, ClassComponent) 191 | 192 | const node = TestUtils.renderIntoDocument( 193 | 194 | ) 195 | 196 | testActions.updateFoo('Baz') 197 | 198 | const span = TestUtils.findRenderedDOMComponentWithTag(node, 'span') 199 | 200 | assert(span.props.foo === 'Baz') 201 | }, 202 | 203 | 'componentDidConnect hook is called '() { 204 | let componentDidConnect = false 205 | class ClassComponent extends React.Component { 206 | render() { 207 | return 208 | } 209 | } 210 | const WrappedComponent = connectToStores({ 211 | getStores() { 212 | return [testStore] 213 | }, 214 | getPropsFromStores(props) { 215 | return testStore.getState() 216 | }, 217 | componentDidConnect() { 218 | componentDidConnect = true 219 | } 220 | }, ClassComponent) 221 | const node = TestUtils.renderIntoDocument( 222 | 223 | ) 224 | assert(componentDidConnect === true) 225 | }, 226 | 227 | 'Component receives all updates'(done) { 228 | let componentDidConnect = false 229 | class ClassComponent extends React.Component { 230 | static getStores() { 231 | return [testStore] 232 | } 233 | static getPropsFromStores(props) { 234 | return testStore.getState() 235 | } 236 | static componentDidConnect() { 237 | testActions.updateFoo('Baz') 238 | componentDidConnect = true 239 | } 240 | componentDidUpdate() { 241 | assert(this.props.foo === 'Baz') 242 | done() 243 | } 244 | render() { 245 | return 246 | } 247 | } 248 | 249 | const WrappedComponent = connectToStores(ClassComponent) 250 | 251 | let node = TestUtils.renderIntoDocument( 252 | 253 | ) 254 | 255 | const span = TestUtils.findRenderedDOMComponentWithTag(node, 'span') 256 | assert(componentDidConnect === true) 257 | }, 258 | 259 | 260 | } 261 | } 262 | --------------------------------------------------------------------------------