├── packages ├── fluxible │ ├── tests │ │ ├── .babelrc │ │ ├── fixtures │ │ │ ├── stores │ │ │ │ ├── BarStore.js │ │ │ │ └── FooStore.js │ │ │ └── plugins │ │ │ │ ├── DimensionsContextPluginSync.js │ │ │ │ ├── DimensionsContextPlugin.js │ │ │ │ ├── DimensionsContextPluginPromise.js │ │ │ │ ├── TestApplicationPluginSync.js │ │ │ │ ├── TestApplicationPlugin.js │ │ │ │ └── TestApplicationPluginPromise.js │ │ └── unit │ │ │ └── utils │ │ │ ├── createMockComponentContext.js │ │ │ └── createMockActionContext.js │ ├── .npmignore │ ├── utils │ │ ├── index.js │ │ ├── createMockActionContext.js │ │ ├── createMockComponentContext.js │ │ ├── deprecateComponent.js │ │ ├── MockComponentContext.js │ │ ├── MockActionContext.js │ │ └── callAction.js │ ├── addons │ │ ├── BaseStore.js │ │ ├── createStore.js │ │ └── index.js │ ├── index.js │ ├── docs │ │ └── api │ │ │ ├── addons │ │ │ ├── createStore.md │ │ │ └── BaseStore.md │ │ │ ├── README.md │ │ │ ├── Plugins.md │ │ │ ├── Fluxible.md │ │ │ ├── FluxibleContext.md │ │ │ ├── Stores.md │ │ │ └── Actions.md │ ├── package.json │ ├── LICENSE.md │ ├── UPGRADE.md │ ├── README.md │ ├── lib │ │ └── Fluxible.js │ └── CHANGELOG.md ├── generator-fluxible │ ├── app │ │ ├── templates │ │ │ ├── _buildgitignore │ │ │ ├── _gitignore │ │ │ ├── .babelrc │ │ │ ├── start.js │ │ │ ├── stores │ │ │ │ ├── RouteStore.js │ │ │ │ └── ApplicationStore.js │ │ │ ├── .eslintrc │ │ │ ├── .editorconfig │ │ │ ├── components │ │ │ │ ├── Home.js │ │ │ │ ├── About.js │ │ │ │ ├── Html.js │ │ │ │ ├── Nav.js │ │ │ │ └── Application.js │ │ │ ├── configs │ │ │ │ └── routes.js │ │ │ ├── app.js │ │ │ ├── webpack-dev-server.js │ │ │ ├── client.js │ │ │ ├── webpack.config.production.js │ │ │ ├── package.json │ │ │ ├── webpack.config.js │ │ │ └── server.js │ │ └── index.js │ ├── .yo-rc.json │ ├── .npmignore │ ├── tests │ │ └── unit │ │ │ └── test-app.js │ ├── package.json │ ├── LICENSE.md │ └── README.md ├── fluxible-addons-react │ ├── tests │ │ ├── .babelrc │ │ ├── fixtures │ │ │ └── stores │ │ │ │ ├── BarStore.js │ │ │ │ └── FooStore.js │ │ └── unit │ │ │ └── lib │ │ │ ├── FluxibleComponent.js │ │ │ ├── batchedUpdatePlugin.js │ │ │ ├── provideContext.js │ │ │ └── connectToStores.js │ ├── .npmignore │ ├── CONTRIBUTING.md │ ├── index.js │ ├── docs │ │ └── api │ │ │ ├── batchedUpdatePlugin.md │ │ │ ├── createElementWithContext.md │ │ │ ├── FluxibleComponent.md │ │ │ ├── FluxibleMixin.md │ │ │ ├── connectToStores.md │ │ │ └── provideContext.md │ ├── package.json │ ├── FluxibleComponent.js │ ├── createElementWithContext.js │ ├── CHANGELOG.md │ ├── batchedUpdatePlugin.js │ ├── LICENSE.md │ ├── README.md │ ├── provideContext.js │ ├── connectToStores.js │ └── FluxibleMixin.js ├── fluxible-reducer-store │ ├── tests │ │ ├── .babelrc │ │ ├── fixtures │ │ │ ├── stores │ │ │ │ ├── SingleReducerStore.js │ │ │ │ └── MessageStore.js │ │ │ └── data │ │ │ │ └── messages.js │ │ └── unit │ │ │ └── lib │ │ │ └── createReducerStore.js │ ├── UPGRADE.md │ ├── CHANGELOG.md │ ├── index.js │ ├── .npmignore │ ├── package.json │ ├── README.md │ ├── LICENSE.md │ ├── lib │ │ └── createReducerStore.js │ └── docs │ │ └── api │ │ └── createReducerStore.md └── README.md ├── .gitignore ├── .eslintrc ├── CONTRIBUTING.md ├── README.md ├── .travis.yml ├── docs ├── quick-start.md ├── README.md ├── home.md ├── community │ ├── articles.md │ ├── presentations.md │ ├── libraries.md │ └── reference-applications.md └── faq.md ├── package.json ├── LICENSE.md └── gulpfile.babel.js /packages/fluxible/tests/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "stage": 0 3 | } 4 | -------------------------------------------------------------------------------- /packages/generator-fluxible/app/templates/_buildgitignore: -------------------------------------------------------------------------------- 1 | * 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | artifacts 3 | *.log 4 | .jshintrc 5 | -------------------------------------------------------------------------------- /packages/fluxible-addons-react/tests/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "stage": 0 3 | } 4 | -------------------------------------------------------------------------------- /packages/fluxible-reducer-store/tests/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "stage": 0 3 | } 4 | -------------------------------------------------------------------------------- /packages/generator-fluxible/.yo-rc.json: -------------------------------------------------------------------------------- 1 | { 2 | "generator-generator": {} 3 | } -------------------------------------------------------------------------------- /packages/generator-fluxible/app/templates/_gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | -------------------------------------------------------------------------------- /packages/README.md: -------------------------------------------------------------------------------- 1 | Each subfolder is a separate npm package with the corresponding name. 2 | -------------------------------------------------------------------------------- /packages/fluxible-reducer-store/UPGRADE.md: -------------------------------------------------------------------------------- 1 | # Upgrade Guide 2 | 3 | No breaking changes yet. 4 | -------------------------------------------------------------------------------- /packages/fluxible-reducer-store/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## 0.1.0 4 | 5 | First version. 6 | -------------------------------------------------------------------------------- /packages/generator-fluxible/app/templates/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "optional": ["es7.classProperties"] 3 | } 4 | -------------------------------------------------------------------------------- /packages/fluxible/.npmignore: -------------------------------------------------------------------------------- 1 | artifacts 2 | docs 3 | tests 4 | .idea 5 | *.log 6 | .coveralls 7 | .npmignore 8 | .travis.yml 9 | -------------------------------------------------------------------------------- /packages/generator-fluxible/app/templates/start.js: -------------------------------------------------------------------------------- 1 | require('babel/register'); 2 | 3 | module.exports = require('./server'); 4 | -------------------------------------------------------------------------------- /packages/fluxible-reducer-store/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | createReducerStore: require('./lib/createReducerStore') 3 | }; 4 | -------------------------------------------------------------------------------- /packages/generator-fluxible/.npmignore: -------------------------------------------------------------------------------- 1 | artifacts 2 | docs 3 | tests 4 | .idea 5 | *.log 6 | .coveralls 7 | .npmignore 8 | .travis.yml 9 | -------------------------------------------------------------------------------- /packages/fluxible-addons-react/.npmignore: -------------------------------------------------------------------------------- 1 | artifacts 2 | docs 3 | tests 4 | .idea 5 | *.log 6 | .coveralls 7 | .npmignore 8 | .travis.yml 9 | -------------------------------------------------------------------------------- /packages/fluxible-reducer-store/.npmignore: -------------------------------------------------------------------------------- 1 | artifacts 2 | docs 3 | tests 4 | .idea 5 | *.log 6 | .coveralls 7 | .npmignore 8 | .travis.yml 9 | -------------------------------------------------------------------------------- /packages/fluxible/utils/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | createMockActionContext: require('./createMockActionContext'), 3 | createMockComponentContext: require('./createMockComponentContext') 4 | }; 5 | -------------------------------------------------------------------------------- /packages/generator-fluxible/app/templates/stores/RouteStore.js: -------------------------------------------------------------------------------- 1 | import {RouteStore} from 'fluxible-router'; 2 | import routes from '../configs/routes'; 3 | 4 | export default RouteStore.withStaticRoutes(routes); 5 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | --- 2 | env: 3 | node: true 4 | parser: "babel-eslint" 5 | extend: "eslint:recommended" 6 | rules: 7 | indent: [2, 4] 8 | quotes: [2, 'single'] 9 | dot-notation: [2, {allowKeywords: false}] 10 | 11 | -------------------------------------------------------------------------------- /packages/fluxible/addons/BaseStore.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015, Yahoo Inc. 3 | * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. 4 | */ 5 | 'use strict'; 6 | 7 | module.exports = require('dispatchr/addons/BaseStore'); 8 | -------------------------------------------------------------------------------- /packages/fluxible/addons/createStore.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015, Yahoo Inc. 3 | * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. 4 | */ 5 | 'use strict'; 6 | 7 | module.exports = require('dispatchr/addons/createStore'); 8 | -------------------------------------------------------------------------------- /packages/generator-fluxible/app/templates/.eslintrc: -------------------------------------------------------------------------------- 1 | --- 2 | parser: "babel-eslint" 3 | env: 4 | node: true 5 | rules: 6 | strict: 0 7 | indent: [2, 4] 8 | quotes: [2, 'single'] 9 | no-unused-vars: 0 // see https://github.com/babel/babel-eslint/issues/21 10 | -------------------------------------------------------------------------------- /packages/fluxible/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2014, Yahoo! Inc. 3 | * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. 4 | */ 5 | var Fluxible = require('./lib/Fluxible'); 6 | Fluxible.Fluxible = require('./lib/Fluxible'); 7 | 8 | module.exports = Fluxible; 9 | -------------------------------------------------------------------------------- /packages/generator-fluxible/app/templates/.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 4 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /packages/fluxible/addons/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015, Yahoo Inc. 3 | * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. 4 | */ 5 | 'use strict'; 6 | 7 | module.exports = { 8 | BaseStore: require('./BaseStore'), 9 | createStore: require('./createStore') 10 | }; 11 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributing Code to `fluxible` 2 | ------------------------------- 3 | 4 | Please be sure to sign our [CLA][] before you submit pull requests or otherwise contribute to `fluxible`. This protects developers, who rely on [BSD license][]. 5 | 6 | [BSD license]: https://github.com/yahoo/fluxible/blob/master/LICENSE.md 7 | [CLA]: https://yahoocla.herokuapp.com/ 8 | -------------------------------------------------------------------------------- /packages/generator-fluxible/app/templates/components/Home.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | class Home extends React.Component { 4 | render() { 5 | return ( 6 |
7 |

Home

8 |

Welcome to the site!

9 |
10 | ); 11 | } 12 | } 13 | 14 | export default Home; 15 | -------------------------------------------------------------------------------- /packages/generator-fluxible/app/templates/components/About.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | class About extends React.Component { 4 | render() { 5 | return ( 6 |
7 |

About

8 |

This is a description of the site.

9 |
10 | ); 11 | } 12 | } 13 | 14 | export default About; 15 | -------------------------------------------------------------------------------- /packages/fluxible-addons-react/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributing Code to `fluxible` 2 | ------------------------------- 3 | 4 | Please be sure to sign our [CLA][] before you submit pull requests or otherwise contribute to `fluxible`. This protects developers, who rely on [BSD license][]. 5 | 6 | [BSD license]: https://github.com/yahoo/fluxible/blob/master/LICENSE.md 7 | [CLA]: https://yahoocla.herokuapp.com/ 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Fluxible 2 | 3 | This repository is the home of Fluxible and related libraries. 4 | 5 | Join the #fluxible channel of the [Reactiflux](http://reactiflux.com) Discord community. 6 | 7 | ## License 8 | 9 | This software is free to use under the Yahoo Inc. BSD license. 10 | See the [LICENSE file][] for license text and copyright information. 11 | 12 | [LICENSE file]: https://github.com/yahoo/fluxible/blob/master/LICENSE.md 13 | -------------------------------------------------------------------------------- /packages/generator-fluxible/app/templates/configs/routes.js: -------------------------------------------------------------------------------- 1 | export default { 2 | home: { 3 | path: '/', 4 | method: 'get', 5 | page: 'home', 6 | title: 'Home', 7 | handler: require('../components/Home') 8 | }, 9 | about: { 10 | path: '/about', 11 | method: 'get', 12 | page: 'about', 13 | title: 'About', 14 | handler: require('../components/About') 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /packages/fluxible/tests/fixtures/stores/BarStore.js: -------------------------------------------------------------------------------- 1 | var createStore = require('../../../addons/createStore'); 2 | module.exports = createStore({ 3 | storeName: 'BarStore', 4 | handlers: { 5 | 'DOUBLE_UP': function () { 6 | this.bar += this.bar; 7 | this.emitChange(); 8 | } 9 | }, 10 | initialize: function () { 11 | this.bar = 'baz'; 12 | }, 13 | getBar: function () { 14 | return this.bar; 15 | } 16 | }); 17 | -------------------------------------------------------------------------------- /packages/fluxible/tests/fixtures/stores/FooStore.js: -------------------------------------------------------------------------------- 1 | var createStore = require('../../../addons/createStore'); 2 | 3 | module.exports = createStore({ 4 | storeName: 'FooStore', 5 | handlers: { 6 | 'DOUBLE_UP': function () { 7 | this.foo += this.foo; 8 | this.emitChange(); 9 | } 10 | }, 11 | initialize: function () { 12 | this.foo = 'bar'; 13 | }, 14 | getFoo: function () { 15 | return this.foo; 16 | } 17 | }); 18 | -------------------------------------------------------------------------------- /packages/fluxible-addons-react/tests/fixtures/stores/BarStore.js: -------------------------------------------------------------------------------- 1 | var createStore = require('fluxible/addons/createStore'); 2 | module.exports = createStore({ 3 | storeName: 'BarStore', 4 | handlers: { 5 | 'DOUBLE_UP': function () { 6 | this.bar += this.bar; 7 | this.emitChange(); 8 | } 9 | }, 10 | initialize: function () { 11 | this.bar = 'baz'; 12 | }, 13 | getBar: function () { 14 | return this.bar; 15 | } 16 | }); 17 | -------------------------------------------------------------------------------- /packages/generator-fluxible/app/templates/app.js: -------------------------------------------------------------------------------- 1 | import Fluxible from 'fluxible'; 2 | import Application from './components/Application'; 3 | import ApplicationStore from './stores/ApplicationStore'; 4 | import RouteStore from './stores/RouteStore'; 5 | 6 | // create new fluxible instance 7 | const app = new Fluxible({ 8 | component: Application 9 | }); 10 | 11 | // register stores 12 | app.registerStore(RouteStore); 13 | app.registerStore(ApplicationStore); 14 | 15 | module.exports = app; 16 | -------------------------------------------------------------------------------- /packages/fluxible-addons-react/tests/fixtures/stores/FooStore.js: -------------------------------------------------------------------------------- 1 | var createStore = require('fluxible/addons/createStore'); 2 | 3 | module.exports = createStore({ 4 | storeName: 'FooStore', 5 | handlers: { 6 | 'DOUBLE_UP': function () { 7 | this.foo += this.foo; 8 | this.emitChange(); 9 | } 10 | }, 11 | initialize: function () { 12 | this.foo = 'bar'; 13 | }, 14 | getFoo: function () { 15 | return this.foo; 16 | } 17 | }); 18 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | matrix: 4 | allow_failures: 5 | - node_js: "iojs" 6 | node_js: 7 | - "iojs" 8 | - "0.12" 9 | - "0.10" 10 | env: 11 | - TEST_DIR=packages/fluxible 12 | - TEST_DIR=packages/fluxible-reducer-store 13 | - TEST_DIR=packages/fluxible-addons-react 14 | - TEST_DIR=packages/generator-fluxible 15 | script: cd $TEST_DIR && npm test 16 | after_success: 17 | - "npm run cover" 18 | - "cat artifacts/lcov.info | ./node_modules/coveralls/bin/coveralls.js" 19 | -------------------------------------------------------------------------------- /packages/fluxible-addons-react/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015, Yahoo Inc. 3 | * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. 4 | */ 5 | 'use strict'; 6 | 7 | module.exports = { 8 | batchedUpdatePlugin: require('./batchedUpdatePlugin'), 9 | connectToStores: require('./connectToStores'), 10 | createElementWithContext: require('./createElementWithContext'), 11 | FluxibleComponent: require('./FluxibleComponent'), 12 | FluxibleMixin: require('./FluxibleMixin'), 13 | provideContext: require('./provideContext') 14 | }; 15 | -------------------------------------------------------------------------------- /packages/fluxible-reducer-store/tests/fixtures/stores/SingleReducerStore.js: -------------------------------------------------------------------------------- 1 | import {createReducerStore} from '../../../index'; 2 | 3 | var SingleReducerStore = createReducerStore({ 4 | storeName: 'SingleReducerStore', 5 | reducer: (state, payload, type) => { 6 | if ('RECEIVE_MESSAGES' === type) { 7 | return { 8 | ...state, 9 | count: state.count + (payload.length) 10 | }; 11 | } 12 | return state; 13 | }, 14 | initialState: { 15 | count: 0 16 | } 17 | }); 18 | 19 | export default SingleReducerStore; 20 | -------------------------------------------------------------------------------- /packages/generator-fluxible/app/templates/webpack-dev-server.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var WebpackDevServer = require('webpack-dev-server'); 3 | var config = require('./webpack.config'); 4 | 5 | new WebpackDevServer(webpack(config), { 6 | publicPath: config.output.publicPath, 7 | hot: true, 8 | historyApiFallback: true, 9 | //quiet: true, 10 | proxy: { 11 | '*': { target: 'http://localhost:3001' } 12 | } 13 | }).listen(3000, function () { 14 | shell.env.PORT = shell.env.PORT || 3001; 15 | shell.exec('"./node_modules/.bin/nodemon" start.js -e js,jsx', function () {}); 16 | console.log('Webpack Dev Server listening on port 3000'); 17 | }); 18 | -------------------------------------------------------------------------------- /packages/fluxible-addons-react/docs/api/batchedUpdatePlugin.md: -------------------------------------------------------------------------------- 1 | # batchedUpdatePlugin 2 | 3 | ```js 4 | import batchedUpdatePlugin from 'fluxible-addons-react/batchedUpdatePlugin'; 5 | ``` 6 | 7 | `batchedUpdatePlugin` is a Fluxible plugin that will batch React `setState` 8 | calls together when they are part of the same `dispatch`. This can improve 9 | performance as there will be less re-renders for components that listen 10 | to multiple stores that react to the same dispatch command. 11 | 12 | ## Example 13 | 14 | The plugin is added to Fluxible immediately after instantiation as follows: 15 | 16 | ```js 17 | var app = new Fluxible(); 18 | app.plug(batchedUpdatePlugin()); 19 | ``` 20 | 21 | This will wrap the `actionContext.dispatch` function with a call to 22 | `React.addons.batchedUpdate`. 23 | -------------------------------------------------------------------------------- /docs/quick-start.md: -------------------------------------------------------------------------------- 1 | # Quick Start 2 | 3 | ```bash 4 | npm install -g yo generator-fluxible 5 | ``` 6 | 7 | To use the generator, create a directory and cd into it. Then run `yo fluxible` which will create a working Fluxible application. To start the application, run `npm run dev`. View it in a browser at http://localhost:3000. 8 | 9 | ```bash 10 | mkdir example && cd example 11 | yo fluxible 12 | npm run dev 13 | ``` 14 | 15 | `open http://localhost:3000` 16 | 17 | This will generate a simple application that demonstrates the basics of using Fluxible: routing, store dehydration from server, and client rehydrating. 18 | 19 | From here, we recommend learning about [stores](../packages/fluxible/docs/api/Stores.md), [actions](../packages/fluxible/docs/api/Actions.md), and React integration with your [components](../packages/fluxible/docs/api/Components.md). 20 | -------------------------------------------------------------------------------- /packages/fluxible/utils/createMockActionContext.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2014, Yahoo! Inc. 3 | * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. 4 | */ 5 | 'use strict'; 6 | 7 | var dispatchr = require('dispatchr'); 8 | var MockActionContextClass = require('./MockActionContext'); 9 | 10 | module.exports = function createMockActionContext(options) { 11 | options = options || {}; 12 | options.mockActionContextClass = options.mockActionContextClass || MockActionContextClass; 13 | options.stores = options.stores || []; 14 | options.dispatcher = options.dispatcher || dispatchr.createDispatcher({ 15 | stores: options.stores 16 | }); 17 | options.dispatcherContext = options.dispatcherContext || options.dispatcher.createContext(); 18 | 19 | return new options.mockActionContextClass(options.dispatcherContext); 20 | }; 21 | -------------------------------------------------------------------------------- /packages/fluxible/utils/createMockComponentContext.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2014, Yahoo! Inc. 3 | * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. 4 | */ 5 | 'use strict'; 6 | 7 | var dispatchr = require('dispatchr'); 8 | var MockComponentContextClass = require('./MockComponentContext'); 9 | 10 | module.exports = function createMockComponentContext(options) { 11 | options = options || {}; 12 | options.mockComponentContextClass = options.mockComponentContextClass || MockComponentContextClass; 13 | options.stores = options.stores || []; 14 | options.dispatcher = options.dispatcher || dispatchr.createDispatcher({ 15 | stores: options.stores 16 | }); 17 | options.dispatcherContext = options.dispatcherContext || options.dispatcher.createContext(); 18 | 19 | return new options.mockComponentContextClass(options.dispatcherContext); 20 | }; 21 | -------------------------------------------------------------------------------- /packages/fluxible-addons-react/docs/api/createElementWithContext.md: -------------------------------------------------------------------------------- 1 | # createElementWithContext(context, props) 2 | 3 | ```js 4 | import createElementWithContext from 'fluxible-addons-react/createElementWithContext'; 5 | ``` 6 | 7 | Convenience method for instantiating the Fluxible app's top level React 8 | component (if provided in the constructor) with the given props with an 9 | additional `context` key containing a ComponentContext. 10 | 11 | ```js 12 | const app = new Fluxible({ 13 | component: MyComponent 14 | }); 15 | const context = app.createContext(); 16 | const markup = ReactDOM.renderToString(createElementWithContext(context, props)); 17 | ``` 18 | 19 | This is the same as the following: 20 | 21 | ```js 22 | const markup = ReactDOM.renderToString( 23 | 24 | 25 | 26 | ); 27 | ``` 28 | -------------------------------------------------------------------------------- /packages/fluxible/docs/api/addons/createStore.md: -------------------------------------------------------------------------------- 1 | # createStore 2 | 3 | ```js 4 | import createStore from 'fluxible-addons-react/createStore'; 5 | ``` 6 | 7 | A helper method similar to `React.createClass` but for creating stores that extend [`BaseStore`](BaseStore.md). Also supports mixins. 8 | 9 | ## Example 10 | 11 | ```js 12 | export default createStore({ 13 | storeName: 'ApplicationStore', 14 | handlers: { 15 | 'RECEIVE_PAGE': 'handleReceivePage' 16 | }, 17 | handleReceivePage: function (payload) { 18 | this.currentPageName = payload.pageName; 19 | this.emitChange(); 20 | }, 21 | getCurrentPage: function () { 22 | return this.currentPageName; 23 | }, 24 | dehydrate: function () { 25 | return { 26 | currentPageName: this.currentPageName 27 | }; 28 | }, 29 | rehydrate: function (state) { 30 | this.currentPageName = state.currentPageName; 31 | } 32 | }); 33 | ``` 34 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Docs 2 | 3 | If you want to jump right into building your application, head over to our [Quick Start Guide](quick-start.md). 4 | 5 | If you want to look at example applications, check out our examples: 6 | 7 | - [Fluxible.io](https://github.com/yahoo/fluxible.io) - Our very own documentation site written with Fluxible. 8 | - [Chat](https://github.com/yahoo/flux-examples/tree/master/chat) - Port of [Facebook's Flux chat example](https://github.com/facebook/flux/tree/master/examples/). 9 | - [Fluxible Router](https://github.com/yahoo/flux-examples/tree/master/fluxible-router) - Simple isomorphic routing in Flux flow. 10 | - [React Router](https://github.com/yahoo/flux-examples/tree/master/react-router) - Simple isomorphic routing with React router. 11 | - [TodoMVC](https://github.com/yahoo/flux-examples/tree/master/todo) - [TodoMVC](https://github.com/tastejs/todomvc) example using Fluxible. 12 | 13 | Or check out some [community applications](community/reference-applications.md). 14 | -------------------------------------------------------------------------------- /packages/fluxible/utils/deprecateComponent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015, Yahoo Inc. 3 | * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. 4 | */ 5 | 'use strict'; 6 | 7 | var React = require('react'); 8 | 9 | /** 10 | * Deprecates a component by logging a warning message when used 11 | * @method deprecateComponent 12 | * @param {React.Component} Component component to wrap 13 | * @param {string} warningMessage Custom contextTypes to add 14 | * @returns {React.Component} 15 | */ 16 | module.exports = function deprecateComponent(Component, warningMessage) { 17 | var DeprecationComponent = React.createClass({ 18 | displayName: 'DeprecationComponent', 19 | 20 | componentWillMount: function () { 21 | console.warn(warningMessage); 22 | }, 23 | 24 | render: function () { 25 | return React.createElement(Component, this.props); 26 | } 27 | }); 28 | 29 | return DeprecationComponent; 30 | }; 31 | -------------------------------------------------------------------------------- /packages/generator-fluxible/app/templates/components/Html.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ApplicationStore from '../stores/ApplicationStore'; 3 | 4 | class Html extends React.Component { 5 | render() { 6 | return ( 7 | 8 | 9 | 10 | {this.props.context.getStore(ApplicationStore).getPageTitle()} 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | ); 21 | } 22 | } 23 | 24 | export default Html; 25 | -------------------------------------------------------------------------------- /packages/fluxible/utils/MockComponentContext.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2014, Yahoo! Inc. 3 | * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. 4 | */ 5 | 'use strict'; 6 | 7 | var createMockActionContext = require('./createMockActionContext'); 8 | function noop () {} 9 | 10 | function MockComponentContext (dispatcherContext) { 11 | this.dispatcherContext = dispatcherContext; 12 | this.executeActionCalls = []; 13 | this.getStore = this.getStore.bind(this); 14 | this.executeAction = this.executeAction.bind(this); 15 | } 16 | 17 | MockComponentContext.prototype.getStore = function (name) { 18 | return this.dispatcherContext.getStore(name); 19 | }; 20 | 21 | MockComponentContext.prototype.executeAction = function (action, payload) { 22 | this.executeActionCalls.push({ 23 | action: action, 24 | payload: payload 25 | }); 26 | action(createMockActionContext({ 27 | dispatcherContext: this.dispatcherContext 28 | }), payload, noop); 29 | }; 30 | 31 | module.exports = MockComponentContext; 32 | -------------------------------------------------------------------------------- /packages/generator-fluxible/app/templates/stores/ApplicationStore.js: -------------------------------------------------------------------------------- 1 | import BaseStore from 'fluxible/addons/BaseStore'; 2 | import RouteStore from './RouteStore'; 3 | 4 | class ApplicationStore extends BaseStore { 5 | constructor(dispatcher) { 6 | super(dispatcher); 7 | this.pageTitle = ''; 8 | } 9 | handlePageTitle(currentRoute) { 10 | this.dispatcher.waitFor(RouteStore, () => { 11 | if (currentRoute && currentRoute.get('title')) { 12 | this.pageTitle = currentRoute.get('title'); 13 | this.emitChange(); 14 | } 15 | }); 16 | } 17 | getPageTitle() { 18 | return this.pageTitle; 19 | } 20 | dehydrate() { 21 | return { 22 | pageTitle: this.pageTitle 23 | }; 24 | } 25 | rehydrate(state) { 26 | this.pageTitle = state.pageTitle; 27 | } 28 | } 29 | 30 | ApplicationStore.storeName = 'ApplicationStore'; 31 | ApplicationStore.handlers = { 32 | 'NAVIGATE_SUCCESS': 'handlePageTitle' 33 | }; 34 | 35 | export default ApplicationStore; 36 | -------------------------------------------------------------------------------- /packages/fluxible-addons-react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fluxible-addons-react", 3 | "version": "0.2.0", 4 | "description": "Fluxible addons for use with React", 5 | "main": "index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/yahoo/fluxible" 9 | }, 10 | "scripts": { 11 | "test": "../../node_modules/.bin/mocha tests/unit/ --recursive --compilers js:babel/register --reporter spec", 12 | "cover": "../../node_modules/.bin/istanbul cover --dir artifacts -- ../../node_modules/.bin/_mocha tests/unit/ --recursive --compilers js:babel/register --reporter spec", 13 | "lint": "../../node_modules/.bin/eslint *.js tests/" 14 | }, 15 | "dependencies": { 16 | "hoist-non-react-statics": "^1.0.0" 17 | }, 18 | "peerDependencies": { 19 | "fluxible": ">=1.0.0", 20 | "react": "^0.14.0", 21 | "react-dom": "^0.14.0" 22 | }, 23 | "devDependencies": { 24 | "fluxible": ">=1.0.0", 25 | "react": "^0.14.0", 26 | "react-dom": "^0.14.0" 27 | }, 28 | "author": "Michael Ridgway ", 29 | "license": "BSD-3-Clause" 30 | } 31 | -------------------------------------------------------------------------------- /packages/fluxible/utils/MockActionContext.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2014, Yahoo! Inc. 3 | * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. 4 | */ 5 | 'use strict'; 6 | var callAction = require('./callAction'); 7 | 8 | function MockActionContext (dispatcherContext) { 9 | this.dispatcherContext = dispatcherContext; 10 | this.executeActionCalls = []; 11 | this.dispatchCalls = []; 12 | } 13 | 14 | MockActionContext.prototype.getStore = function (name) { 15 | return this.dispatcherContext.getStore(name); 16 | }; 17 | 18 | MockActionContext.prototype.dispatch = function (name, payload) { 19 | this.dispatchCalls.push({ 20 | name: name, 21 | payload: payload 22 | }); 23 | this.dispatcherContext.dispatch(name, payload); 24 | }; 25 | 26 | MockActionContext.prototype.executeAction = function (action, payload, callback) { 27 | this.executeActionCalls.push({ 28 | action: action, 29 | payload: payload 30 | }); 31 | return callAction(action, this, payload, callback); 32 | }; 33 | 34 | module.exports = MockActionContext; 35 | -------------------------------------------------------------------------------- /packages/generator-fluxible/tests/unit/test-app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015, Yahoo! Inc. 3 | * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. 4 | */ 5 | /*globals describe,before,it*/ 6 | 7 | var path = require('path'); 8 | var assert = require('yeoman-generator').assert; 9 | var helpers = require('yeoman-generator').test; 10 | var os = require('os'); 11 | 12 | describe('generator-fluxible', function () { 13 | describe('app', function () { 14 | before(function (done) { 15 | helpers.run(path.join(__dirname, '../../app')) 16 | .inDir(path.join(os.tmpdir(), './temp-test')) 17 | .withOptions({ 'skip-install': true }) 18 | .on('end', done); 19 | }); 20 | 21 | it('creates files', function () { 22 | assert.file([ 23 | 'package.json', 24 | '.editorconfig', 25 | '.babelrc', 26 | '.eslintrc', 27 | 'app.js', 28 | 'components/Application.js' 29 | ]); 30 | }); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /packages/fluxible-reducer-store/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fluxible-reducer-store", 3 | "version": "0.1.0", 4 | "description": "Reducer store for Fluxible", 5 | "main": "index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/yahoo/fluxible" 9 | }, 10 | "scripts": { 11 | "test": "../../node_modules/.bin/mocha tests/unit/ --recursive --compilers js:babel/register --reporter spec", 12 | "cover": "../../node_modules/.bin/istanbul cover --dir artifacts -- ../../node_modules/.bin/_mocha tests/unit/ --recursive --compilers js:babel/register --reporter spec", 13 | "lint": "../../node_modules/.bin/eslint *.js" 14 | }, 15 | "dependencies": { 16 | "fluxible": ">0.3.0" 17 | }, 18 | "author": "Michael Ridgway ", 19 | "contributors": [], 20 | "licenses": [ 21 | { 22 | "type": "BSD-3-Clause", 23 | "url": "https://github.com/yahoo/fluxible/blob/master/LICENSE.md" 24 | } 25 | ], 26 | "keywords": [ 27 | "yahoo", 28 | "flux", 29 | "react", 30 | "fluxible", 31 | "isomorphic", 32 | "reducer" 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /packages/fluxible-addons-react/FluxibleComponent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015, Yahoo Inc. 3 | * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. 4 | */ 5 | 'use strict'; 6 | 7 | var React = require('react'); 8 | 9 | var FluxibleComponent = React.createClass({ 10 | displayName: 'FluxibleComponent', 11 | propTypes: { 12 | context: React.PropTypes.object.isRequired 13 | }, 14 | 15 | childContextTypes: { 16 | executeAction: React.PropTypes.func.isRequired, 17 | getStore: React.PropTypes.func.isRequired 18 | }, 19 | 20 | /** 21 | * Provides the current context as a child context 22 | * @method getChildContext 23 | */ 24 | getChildContext: function () { 25 | return { 26 | getStore: this.props.context.getStore, 27 | executeAction: this.props.context.executeAction 28 | }; 29 | }, 30 | 31 | render: function () { 32 | return React.cloneElement(this.props.children, { 33 | context: this.props.context 34 | }); 35 | } 36 | }); 37 | 38 | module.exports = FluxibleComponent; 39 | -------------------------------------------------------------------------------- /packages/generator-fluxible/app/templates/components/Nav.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { NavLink } from 'fluxible-router'; 3 | 4 | class Nav extends React.Component { 5 | render() { 6 | const selected = this.props.currentRoute; 7 | const links = this.props.links; 8 | 9 | const linkHTML = Object.keys(links).map((name) => { 10 | var className = ''; 11 | var link = links[name]; 12 | 13 | if (selected && selected.get('name') === name) { 14 | className = 'pure-menu-selected'; 15 | } 16 | 17 | return ( 18 |
  • 19 | {link.title} 20 |
  • 21 | ); 22 | }); 23 | 24 | return ( 25 | 28 | ); 29 | } 30 | } 31 | 32 | Nav.defaultProps = { 33 | selected: null, 34 | links: {} 35 | }; 36 | 37 | export default Nav; 38 | -------------------------------------------------------------------------------- /packages/generator-fluxible/app/templates/client.js: -------------------------------------------------------------------------------- 1 | /*global document, window */ 2 | 3 | import ReactDOM from 'react-dom'; 4 | import React from 'react'; 5 | import debug from 'debug'; 6 | import { createElementWithContext } from 'fluxible-addons-react'; 7 | import app from './app'; 8 | 9 | const debugClient = debug('<%= name %>'); 10 | const dehydratedState = window.App; // Sent from the server 11 | 12 | window.React = ReactDOM; // For chrome dev tool support 13 | 14 | // expose debug object to browser, so that it can be enabled/disabled from browser: 15 | // https://github.com/visionmedia/debug#browser-support 16 | window.fluxibleDebug = debug; 17 | 18 | debugClient('rehydrating app'); 19 | 20 | // pass in the dehydrated server state from server.js 21 | app.rehydrate(dehydratedState, (err, context) => { 22 | if (err) { 23 | throw err; 24 | } 25 | window.context = context; 26 | const mountNode = document.getElementById('app'); 27 | 28 | debugClient('React Rendering'); 29 | ReactDOM.render( 30 | createElementWithContext(context), 31 | mountNode, 32 | () => debugClient('React Rendered') 33 | ); 34 | }); 35 | -------------------------------------------------------------------------------- /docs/home.md: -------------------------------------------------------------------------------- 1 | # Features 2 | 3 | ## Singleton-free for server rendering 4 | 5 | [Stores](../packages/fluxible/docs/api/Stores.md) are classes that are instantiated per request or client session. This ensures that the stores are isolated and do not bleed information between requests. 6 | 7 | ## Dehydration/Rehydration 8 | 9 | [Stores](../packages/fluxible/docs/api/Stores.md) can provide `dehydrate` and `rehydrate` so that you can propagate the initial server state to the client. 10 | 11 | ## React Integration 12 | 13 | Helper utilities for integrating your Fluxible app into React [components](../packages/fluxible/docs/api/Components.md) with less boilerplate. 14 | 15 | ## Flow Regulation 16 | 17 | [FluxibleContext](../packages/fluxible/docs/api/FluxibleContext.md) restricts access to your Flux methods so that you can't break out of the unidirectional flow. 18 | 19 | ## Pluggable 20 | 21 | Want to add your own interfaces to the Flux flow? [Plugins](../packages/fluxible/docs/api/Plugins.md) allow you to add methods to any of the contexts. 22 | 23 | ## Updated for React 0.13 24 | 25 | Updated to follow React 0.13's changes and the deprecations coming in the next version of React. 26 | -------------------------------------------------------------------------------- /packages/fluxible/utils/callAction.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2014, Yahoo! Inc. 3 | * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. 4 | */ 5 | /* global Promise */ 6 | 'use strict'; 7 | var isPromise = require('is-promise'); 8 | 9 | /** 10 | * Call an action supporting Promise expectations on invocation. 11 | * 12 | * If done callback supplied, that indicates non-Promise invocation expectation, 13 | * otherwise, Promise invocation. 14 | */ 15 | function callAction (action, context, payload, done) { 16 | if (done) { 17 | return action(context, payload, done); 18 | } 19 | 20 | return new Promise(function (resolve, reject) { 21 | try { 22 | var syncResult = action(context, payload, function (err, result) { 23 | if (err) { 24 | reject(err); 25 | } else { 26 | resolve(result); 27 | } 28 | }); 29 | if (isPromise(syncResult)) { 30 | syncResult.then(resolve, reject); 31 | } else if (action.length < 3) { 32 | resolve(syncResult); 33 | } 34 | } catch (e) { 35 | reject(e); 36 | } 37 | }); 38 | } 39 | 40 | module.exports = callAction; 41 | -------------------------------------------------------------------------------- /packages/fluxible/tests/fixtures/plugins/DimensionsContextPluginSync.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2014, Yahoo! Inc. 3 | * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. 4 | */ 5 | 'use strict'; 6 | 7 | module.exports = function (dims) { 8 | var dimensions = dims; 9 | return { 10 | name: 'DimensionsPlugin', 11 | plugActionContext: function (actionContext) { 12 | actionContext.getDimensions = function () { 13 | return dimensions; 14 | }; 15 | }, 16 | plugComponentContext: function (componentContext) { 17 | componentContext.getDimensions = function () { 18 | return dimensions; 19 | }; 20 | }, 21 | plugStoreContext: function (storeContext) { 22 | storeContext.getDimensions = function () { 23 | return dimensions; 24 | }; 25 | }, 26 | dehydrate: function () { 27 | return { 28 | dimensions: dimensions 29 | }; 30 | }, 31 | rehydrate: function (state) { 32 | dimensions = state.dimensions; 33 | } 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /packages/fluxible/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fluxible", 3 | "version": "1.0.0", 4 | "description": "A pluggable container for isomorphic flux applications", 5 | "main": "index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/yahoo/fluxible" 9 | }, 10 | "scripts": { 11 | "test": "../../node_modules/.bin/mocha tests/unit/ --recursive --compilers js:babel/register --reporter spec", 12 | "cover": "../../node_modules/.bin/istanbul cover --dir artifacts -- ../../node_modules/.bin/_mocha tests/unit/ --recursive --compilers js:babel/register --reporter spec", 13 | "lint": "../../node_modules/.bin/eslint lib/ addons/" 14 | }, 15 | "dependencies": { 16 | "debug": "^2.0.0", 17 | "dispatchr": "^0.3.1", 18 | "is-promise": "^2.0.0", 19 | "setimmediate": "^1.0.2" 20 | }, 21 | "author": "Michael Ridgway ", 22 | "contributors": [], 23 | "licenses": [ 24 | { 25 | "type": "BSD-3-Clause", 26 | "url": "https://github.com/yahoo/fluxible/blob/master/LICENSE.md" 27 | } 28 | ], 29 | "keywords": [ 30 | "yahoo", 31 | "flux", 32 | "react", 33 | "fluxible", 34 | "isomorphic" 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /packages/generator-fluxible/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "generator-fluxible", 3 | "version": "1.2.0", 4 | "description": "Yeoman generator for Fluxible", 5 | "main": "app/index.js", 6 | "repository": "yahoo/fluxible", 7 | "author": { 8 | "name": "Seth Bertalotto", 9 | "email": "sbertal@yahoo-inc.com" 10 | }, 11 | "engines": { 12 | "node": ">=0.10.0" 13 | }, 14 | "scripts": { 15 | "cover": "../../node_modules/.bin/istanbul cover --dir artifacts -- ../../node_modules/.bin/_mocha test --recursive --reporter spec", 16 | "lint": "../../node_modules/.bin/eslint app/ test/", 17 | "test": "../../node_modules/.bin/mocha tests/unit --recursive --reporter=spec" 18 | }, 19 | "precommit": [ 20 | "lint", 21 | "test" 22 | ], 23 | "files": [ 24 | "app" 25 | ], 26 | "keywords": [ 27 | "yeoman-generator", 28 | "flux", 29 | "react", 30 | "fluxible" 31 | ], 32 | "dependencies": { 33 | "chalk": "^1.0.0", 34 | "underscore.string": "^3.0.2", 35 | "yeoman-generator": "^0.20.1", 36 | "yosay": "^1.0.2" 37 | }, 38 | "peerDependencies": { 39 | "yo": ">=1.0.0" 40 | }, 41 | "devDependencies": { 42 | "yo": ">=1.0.0" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /docs/community/articles.md: -------------------------------------------------------------------------------- 1 | # Articles and Blogs 2 | 3 | Take a look at some of the articles that our community has written. Edit this 4 | page on GitHub to list yours! 5 | 6 | * 2015-09-24 - [How to Migrate React to Isomorphic Rendering](https://medium.com/building-coursera/how-to-migrate-react-to-isomorphic-rendering-88347ba653a5) 7 | * 2015-08-27 - [Building a Site Using Isomorphic Flux with Fluxible](http://jacksoncmorgan.com/blog/fluxiblemaster) 8 | * 2015-06-10 - [yahoo/fluxible による SPA + Server Rendering の概観](http://havelog.ayumusato.com/develop/javascript/e675-spa_and_server_rendering_with_fluxible.html) 9 | * 2015-04-14 - [Managing Context In a Fluxible App Using React-Router](http://www.ian-thomas.net/managing-context-in-a-fluxible-app-using-react-router/) 10 | * 2015-03-12 - [How I use fluxible with Hapi](http://danecando.com/how-i-use-fluxible-with-hapi/) 11 | * 2015-02-20 - [Exploring Isomorphic JavaScript](http://nicolashery.com/exploring-isomorphic-javascript/) 12 | * 2014-11-30 - [Isomorphic React + Flux using Yahoo's Fluxible](http://dev.alexishevia.com/2014/11/isomorphic-react-flux-using-yahoos.html) 13 | * 2014-11-06 - [Bringing Flux to the Server](/blog/2014-11-06-bringing-flux-to-the-server.md) 14 | -------------------------------------------------------------------------------- /packages/fluxible/tests/fixtures/plugins/DimensionsContextPlugin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2014, Yahoo! Inc. 3 | * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. 4 | */ 5 | 'use strict'; 6 | 7 | module.exports = function (dims) { 8 | var dimensions = dims; 9 | return { 10 | name: 'DimensionsPlugin', 11 | plugActionContext: function (actionContext) { 12 | actionContext.getDimensions = function () { 13 | return dimensions; 14 | }; 15 | }, 16 | plugComponentContext: function (componentContext) { 17 | componentContext.getDimensions = function () { 18 | return dimensions; 19 | }; 20 | }, 21 | plugStoreContext: function (storeContext) { 22 | storeContext.getDimensions = function () { 23 | return dimensions; 24 | }; 25 | }, 26 | dehydrate: function () { 27 | return { 28 | dimensions: dimensions 29 | }; 30 | }, 31 | rehydrate: function (state, done) { 32 | dimensions = state.dimensions; 33 | setImmediate(done); 34 | } 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /packages/fluxible-addons-react/createElementWithContext.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015, Yahoo Inc. 3 | * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. 4 | */ 5 | 'use strict'; 6 | 7 | var React = require('react'); 8 | var FluxibleComponent = require('./FluxibleComponent'); 9 | 10 | /** 11 | * Creates an instance of the app level component with given props and a proper component 12 | * context. 13 | * @param {FluxibleContext} fluxibleContext 14 | * @param {Object} props 15 | * @return {ReactElement} 16 | */ 17 | module.exports = function createElement(fluxibleContext, props) { 18 | var Component = fluxibleContext.getComponent(); 19 | if (!Component) { 20 | throw new Error('A top-level component was not passed to the Fluxible' + 21 | ' constructor.'); 22 | } 23 | if (Component.displayName && -1 !== Component.displayName.indexOf('ContextProvider')) { 24 | return React.createElement(Component, Object.assign({}, { 25 | context: fluxibleContext.getComponentContext() 26 | }, props)); 27 | } 28 | var componentInstance = React.createElement(Component, props); 29 | return React.createElement(FluxibleComponent, { 30 | context: fluxibleContext.getComponentContext() 31 | }, componentInstance); 32 | }; 33 | -------------------------------------------------------------------------------- /packages/fluxible/tests/fixtures/plugins/DimensionsContextPluginPromise.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2014, Yahoo! Inc. 3 | * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. 4 | */ 5 | 'use strict'; 6 | 7 | module.exports = function (dims) { 8 | var dimensions = dims; 9 | return { 10 | name: 'DimensionsPlugin', 11 | plugActionContext: function (actionContext) { 12 | actionContext.getDimensions = function () { 13 | return dimensions; 14 | }; 15 | }, 16 | plugComponentContext: function (componentContext) { 17 | componentContext.getDimensions = function () { 18 | return dimensions; 19 | }; 20 | }, 21 | plugStoreContext: function (storeContext) { 22 | storeContext.getDimensions = function () { 23 | return dimensions; 24 | }; 25 | }, 26 | dehydrate: function () { 27 | return { 28 | dimensions: dimensions 29 | }; 30 | }, 31 | rehydrate: function (state) { 32 | return new Promise(function (resolve) { 33 | dimensions = state.dimensions; 34 | resolve(); 35 | }); 36 | } 37 | } 38 | }; 39 | -------------------------------------------------------------------------------- /packages/generator-fluxible/app/templates/components/Application.js: -------------------------------------------------------------------------------- 1 | /*globals document*/ 2 | 3 | import React from 'react'; 4 | import Nav from './Nav'; 5 | import ApplicationStore from '../stores/ApplicationStore'; 6 | import { connectToStores, provideContext } from 'fluxible-addons-react'; 7 | import { handleHistory } from 'fluxible-router'; 8 | import pages from '../configs/routes'; 9 | 10 | class Application extends React.Component { 11 | render() { 12 | var Handler = this.props.currentRoute.get('handler'); 13 | 14 | return ( 15 |
    16 |
    19 | ); 20 | } 21 | 22 | componentDidUpdate(prevProps, prevState) { 23 | const newProps = this.props; 24 | if (newProps.pageTitle === prevProps.pageTitle) { 25 | return; 26 | } 27 | document.title = newProps.pageTitle; 28 | } 29 | } 30 | 31 | export default provideContext(handleHistory(connectToStores( 32 | Application, 33 | [ApplicationStore], 34 | function (context, props) { 35 | var appStore = context.getStore(ApplicationStore); 36 | return { 37 | pageTitle: appStore.getPageTitle() 38 | }; 39 | } 40 | ))); 41 | -------------------------------------------------------------------------------- /packages/generator-fluxible/app/templates/webpack.config.production.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var path = require('path'); 3 | 4 | var webpackConfig = { 5 | resolve: { 6 | extensions: ['', '.js'] 7 | }, 8 | entry: [ 9 | './client.js' 10 | ], 11 | output: { 12 | path: path.resolve('./build/js'), 13 | publicPath: '/public/js/', 14 | filename: 'main.min.js' 15 | }, 16 | module: { 17 | loaders: [ 18 | { 19 | test: /\.(js|jsx)$/, 20 | exclude: /node_modules/, 21 | loaders: [ 22 | require.resolve('babel-loader') 23 | ] 24 | }, 25 | { test: /\.json$/, loader: 'json-loader'} 26 | ] 27 | }, 28 | node: { 29 | setImmediate: false 30 | }, 31 | plugins: [ 32 | new webpack.DefinePlugin({ 33 | 'process.env': { 34 | NODE_ENV: JSON.stringify('production') 35 | } 36 | }), 37 | new webpack.optimize.DedupePlugin(), 38 | new webpack.optimize.UglifyJsPlugin({ 39 | compress: { 40 | warnings: false 41 | } 42 | }) 43 | ], 44 | devtool: 'source-map' 45 | }; 46 | 47 | module.exports = webpackConfig; 48 | -------------------------------------------------------------------------------- /packages/generator-fluxible/app/templates/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "<%= name %>", 3 | "version": "0.0.0", 4 | "private": true, 5 | "main": "start.js", 6 | "scripts": { 7 | "build": "webpack & webpack --config webpack.config.production.js", 8 | "dev": "node webpack-dev-server.js", 9 | "lint": "eslint ./*.js ./**/*.js", 10 | "start": "node start.js" 11 | }, 12 | "dependencies": { 13 | "babel": "^5.8.23", 14 | "babel-core": "^5.8.25", 15 | "body-parser": "^1.6.4", 16 | "compression": "^1.5.1", 17 | "cookie-parser": "^1.3.3", 18 | "csurf": "^1.6.3", 19 | "debug": "^2.0.0", 20 | "express": "^4.3.2", 21 | "express-state": "^1.2.0", 22 | "fluxible": "^1.0.0", 23 | "fluxible-addons-react": "^0.2.0", 24 | "fluxible-plugin-fetchr": "^0.3.0", 25 | "fluxible-router": "^0.3.0", 26 | "react": "^0.14.0", 27 | "react-dom": "^0.14.0", 28 | "serialize-javascript": "^1.0.0", 29 | "serve-favicon": "^2.1.6" 30 | }, 31 | "devDependencies": { 32 | "babel-eslint": "^3.0.1", 33 | "babel-loader": "^5.1.3", 34 | "bundle-loader": "^0.5.0", 35 | "eslint": "^0.24.0", 36 | "json-loader": "^0.5.1", 37 | "nodemon": "^1.2.1", 38 | "react-hot-loader": "^1.2.8", 39 | "shelljs": "^0.5.3", 40 | "webpack": "^1.4.12", 41 | "webpack-dev-server": "^1.6.5" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /packages/fluxible-addons-react/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## 0.2.0 4 | 5 | ### Breaking Changes 6 | 7 | * Upgraded to React 0.14, breaking compatibility with older versions of React 8 | * Removed dependency on `Object.assign` library, must be polyfilled 9 | 10 | ## 0.1.8 11 | 12 | ### Bug Fixes 13 | 14 | * Expose batchUpdatePlugin from default exports 15 | 16 | ## 0.1.7 17 | 18 | ### Features 19 | 20 | * [#21] Add batch update plugin that will batch all setState calls within the same dispatch 21 | 22 | ### Internal 23 | 24 | * [#22] Updated object-assign dependency 25 | * [#23] Updated dev dependencies 26 | 27 | 28 | ## 0.1.6 29 | 30 | ### Bug Fixes 31 | 32 | * [#9] Fixes calling stateGetter when component receives new props 33 | 34 | ## 0.1.5 35 | 36 | ### Bug Fixes 37 | 38 | * [#10] Update fluxible dep and remove unused dep 39 | 40 | ## 0.1.4 41 | 42 | ### Bug Fixes 43 | 44 | * Update fluxible version 45 | 46 | ## 0.1.3 47 | 48 | ### Bug Fixes 49 | 50 | * Allow pre-release versions of fluxible 51 | 52 | ## 0.1.2 53 | 54 | ### Bug Fixes 55 | 56 | * [#2] Remove circular dependency with fluxible 57 | * [#3] Add backwards dependency for old connectToStores with deprecation notices 58 | * [#4] Fix documentation links 59 | 60 | ## 0.1.1 61 | 62 | ### Bug Fixes 63 | 64 | * [#1] Loosen fluxible dependency 65 | 66 | ## 0.1.0 67 | 68 | * Init commit 69 | -------------------------------------------------------------------------------- /packages/generator-fluxible/app/templates/webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var path = require('path'); 3 | 4 | var webpackConfig = { 5 | resolve: { 6 | extensions: ['', '.js', '.jsx'] 7 | }, 8 | entry: [ 9 | 'webpack-dev-server/client?http://localhost:3000', 10 | 'webpack/hot/only-dev-server', 11 | './client.js' 12 | ], 13 | output: { 14 | path: path.resolve('./build/js'), 15 | publicPath: '/public/js/', 16 | filename: 'main.js' 17 | }, 18 | module: { 19 | loaders: [ 20 | { 21 | test: /\.(js|jsx)$/, 22 | exclude: /node_modules/, 23 | loaders: [ 24 | require.resolve('react-hot-loader'), 25 | require.resolve('babel-loader') 26 | ] 27 | }, 28 | { test: /\.json$/, loader: 'json-loader'} 29 | ] 30 | }, 31 | node: { 32 | setImmediate: false 33 | }, 34 | plugins: [ 35 | new webpack.HotModuleReplacementPlugin(), 36 | new webpack.NoErrorsPlugin(), 37 | new webpack.DefinePlugin({ 38 | 'process.env': { 39 | NODE_ENV: JSON.stringify(process.env.NODE_ENV) 40 | } 41 | }) 42 | ], 43 | devtool: 'eval' 44 | }; 45 | 46 | module.exports = webpackConfig; 47 | -------------------------------------------------------------------------------- /packages/fluxible-addons-react/batchedUpdatePlugin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2014, Yahoo! Inc. 3 | * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. 4 | */ 5 | 'use strict'; 6 | var batchedUpdates = require('react-dom').unstable_batchedUpdates; 7 | 8 | module.exports = function createBatchedUpdatePlugin(options) { 9 | /** 10 | * @class BatchedUpdatePlugin 11 | */ 12 | return { 13 | name: 'BatchedUpdatePlugin', 14 | /** 15 | * Called to plug the FluxContext 16 | * @method plugContext 17 | * @returns {Object} 18 | */ 19 | plugContext: function plugContext() { 20 | return { 21 | /** 22 | * Provides full access to the router in the action context 23 | * @param {Object} actionContext 24 | */ 25 | plugActionContext: function plugActionContext(actionContext) { 26 | var oldDispatch = actionContext.dispatch; 27 | actionContext.dispatch = function () { 28 | var args = arguments; 29 | batchedUpdates(function () { 30 | oldDispatch.apply(actionContext, args); 31 | }); 32 | }; 33 | } 34 | }; 35 | } 36 | }; 37 | }; 38 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fluxible-repo", 3 | "version": "1.0.0", 4 | "private": true, 5 | "description": "Fluxible monorepo", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/yahoo/fluxible" 9 | }, 10 | "scripts": { 11 | "test": "mocha packages/*/tests/unit/ --recursive --compilers js:babel/register --reporter spec", 12 | "cover": "istanbul cover --dir artifacts -- _mocha packages/*/tests/unit/ --recursive --compilers js:babel/register --reporter spec", 13 | "lint": "eslint *.js", 14 | "postinstall": "gulp install", 15 | "preversion": "echo \"Error: version should be called using gulp\" && exit 1" 16 | }, 17 | "dependencies": {}, 18 | "devDependencies": { 19 | "async": "^1.4.2", 20 | "babel": "^5.8.23", 21 | "babel-core": "^5.8.25", 22 | "babel-eslint": "^4.1.0", 23 | "chai": "^3.2.0", 24 | "coveralls": "^2.11.1", 25 | "eslint": "^1.3.0", 26 | "eslint-plugin-react": "^3.3.0", 27 | "gulp": "^3.9.0", 28 | "istanbul": "^0.3.2", 29 | "jsdom": "^3.1.2", 30 | "mocha": "^2.0.1", 31 | "mockery": "^1.4.0", 32 | "pre-commit": "^1.0.7", 33 | "react": "^0.14.0", 34 | "react-addons-test-utils": "^0.14.0", 35 | "react-dom": "^0.14.0", 36 | "shelljs": "^0.5.3", 37 | "yargs": "^3.27.0" 38 | }, 39 | "pre-commit": [ 40 | "lint", 41 | "test" 42 | ], 43 | "author": "Michael Ridgway ", 44 | "license": "BSD-3-Clause" 45 | } 46 | -------------------------------------------------------------------------------- /packages/fluxible/docs/api/addons/BaseStore.md: -------------------------------------------------------------------------------- 1 | # BaseStore 2 | 3 | ```js 4 | import BaseStore from 'fluxible/addons/BaseStore'; 5 | ``` 6 | 7 | A base class that you can extend to reduce boilerplate when creating stores. 8 | 9 | ## Built-In Methods 10 | 11 | * `emitChange()` - emits a 'change' event 12 | * `getContext()` - returns the [store context](../FluxibleContext.md#store-context) 13 | * `addChangeListener(callback)` - simple method to add a change listener 14 | * `removeChangeListener(callback)` - removes a change listener 15 | * `shouldDehydrate()` - default implementation that returns true if a `change` event has been emitted 16 | 17 | ## Example 18 | 19 | ```js 20 | class ApplicationStore extends BaseStore { 21 | constructor (dispatcher) { 22 | super(dispatcher); 23 | this.currentPageName = null; 24 | } 25 | 26 | handleReceivePage (payload) { 27 | this.currentPageName = payload.pageName; 28 | this.emitChange(); 29 | } 30 | 31 | getCurrentPageName () { 32 | return this.currentPageName; 33 | } 34 | 35 | // For sending state to the client 36 | dehydrate () { 37 | return { 38 | currentPageName: this.currentPageName 39 | }; 40 | } 41 | 42 | // For rehydrating server state 43 | rehydrate (state) { 44 | this.currentPageName = state.currentPageName; 45 | } 46 | } 47 | 48 | ApplicationStore.storeName = 'ApplicationStore'; 49 | ApplicationStore.handlers = { 50 | 'RECEIVE_PAGE': 'handleReceivePage' 51 | }; 52 | 53 | export default ApplicationStore; 54 | ``` 55 | -------------------------------------------------------------------------------- /docs/community/presentations.md: -------------------------------------------------------------------------------- 1 | # Presentations 2 | 3 | Take a look at talks and presentations we (Yahoo) and the community have done. 4 | 5 | ### Isomorphic Flux - ReactEurope 2015 - Michael Ridgway 6 | 7 | Michael Ridgway presented "Isomorphic Flux" at the [ReactEurope](http://www.react-europe.org/) conference 2015. 8 | 9 | 10 | 11 | ### Flux Panel - ReactConf 2015 - Michael Ridgway 12 | 13 | Our own, [Michael Ridgway](https://twitter.com/theridgway), was part of a panel of Flux experts at ReactConf 2015. 14 | 15 | 16 | 17 | ### Isomorphic and Reactive Applications - NodeSummit 2015 - Peter Marton 18 | 19 | Peter Marton from RisingStack gave a [great overview](https://speakerdeck.com/slashdotpeter/nodesummit-isomorphic-and-reactive-applications) of building isomorphic React applications with Fluxible's architecture. 20 | 21 | 22 | 23 | ### Isomorphic Flux - SF React Meetup, November 2014 - Michael Ridgway 24 | 25 | Presentation of the Fluxible architecture at the SF React Meetup. 26 | 27 | 28 | -------------------------------------------------------------------------------- /packages/fluxible-reducer-store/README.md: -------------------------------------------------------------------------------- 1 | # Fluxible Reducer Store 2 | 3 | [![NPM version](https://badge.fury.io/js/fluxible-reducer-store.svg)](http://badge.fury.io/js/fluxible-reducer-store) 4 | 10 | 11 | A helper method for creating reducer stores for Fluxible. 12 | 13 | ```bash 14 | $ npm install --save fluxible-reducer-store 15 | ``` 16 | 17 | ## Docs 18 | 19 | * [API](https://github.com/yahoo/fluxible/blob/master/packages/fluxible-reducer-store/docs/api/createReducerStore.md) 20 | 21 | ## Browser Compatibility 22 | 23 | Fluxible is written with ES2015 in mind and should be used along with polyfills 24 | for features like [`Object.assign`][objectAssign] in order to support all 25 | browsers. We recommend using [Babel][babel]. 26 | 27 | ## License 28 | 29 | This software is free to use under the Yahoo Inc. BSD license. 30 | See the [LICENSE file][] for license text and copyright information. 31 | 32 | [Promise]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise 33 | [objectAssign]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign 34 | [babel]: https://babeljs.io/ 35 | [LICENSE file]: https://github.com/yahoo/fluxible/blob/master/LICENSE.md 36 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Software License Agreement (BSD License) 2 | ======================================== 3 | 4 | Copyright (c) 2015, Yahoo Inc. All rights reserved. 5 | ---------------------------------------------------- 6 | 7 | Redistribution and use of this software in source and binary forms, with or 8 | without modification, are permitted provided that the following conditions are 9 | met: 10 | 11 | * Redistributions of source code must retain the above copyright notice, this 12 | list of conditions and the following disclaimer. 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | this list of conditions and the following disclaimer in the documentation 15 | and/or other materials provided with the distribution. 16 | * Neither the name of Yahoo Inc. nor the names of this package's contributors 17 | may be used to endorse or promote products derived from this software 18 | without specific prior written permission of Yahoo Inc. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 21 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 22 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 24 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 25 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 26 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 27 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 29 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /packages/fluxible-addons-react/docs/api/FluxibleComponent.md: -------------------------------------------------------------------------------- 1 | # FluxibleComponent 2 | 3 | ```js 4 | import FluxibleComponent from 'fluxible-addons-react/FluxibleComponent'; 5 | ``` 6 | 7 | The `FluxibleComponent` is a wrapper component that will provide all of its children with access to the Fluxible component 8 | context via React's `childContextTypes` and `getChildContext`. This should be used to wrap your top level component. It provides access to the methods on the [component context](../../../../packages/fluxible/docs/api/Components.md#component-context). 9 | 10 | You can get access to these methods by setting the correct `contextTypes` within your component or including the [`FluxibleMixin`](./FluxibleMixin.md) which will add them for you. 11 | 12 | ## Usage 13 | 14 | If you have a component that needs access to the [`ComponentContext`](../../../../packages/fluxible/docs/api/Components.md#component-context) methods: 15 | 16 | ```js 17 | class Component extends React.Component { 18 | constructor(props) { 19 | super(props); 20 | this.state = this.context.getStore(FooStore).getState(); 21 | } 22 | } 23 | Component.contextTypes = { 24 | getStore: React.PropTypes.func.isRequired, 25 | executeAction: React.PropTypes.func.isRequired 26 | }; 27 | ``` 28 | 29 | you can wrap the component with `FluxibleComponent` to provide the correct context: 30 | 31 | ```js 32 | let html = ReactDOM.renderToString( 33 | 34 | 35 | 36 | ); 37 | ``` 38 | 39 | If you are using [`createElementWithContext`](./createElementWithContext.md) this will happen for you automatically: 40 | 41 | ```js 42 | let html = ReactDOM.renderToString(createElementWithContext(context)); 43 | ``` 44 | -------------------------------------------------------------------------------- /packages/fluxible/LICENSE.md: -------------------------------------------------------------------------------- 1 | Software License Agreement (BSD License) 2 | ======================================== 3 | 4 | Copyright (c) 2015, Yahoo Inc. All rights reserved. 5 | ---------------------------------------------------- 6 | 7 | Redistribution and use of this software in source and binary forms, with or 8 | without modification, are permitted provided that the following conditions are 9 | met: 10 | 11 | * Redistributions of source code must retain the above copyright notice, this 12 | list of conditions and the following disclaimer. 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | this list of conditions and the following disclaimer in the documentation 15 | and/or other materials provided with the distribution. 16 | * Neither the name of Yahoo Inc. nor the names of this package's contributors 17 | may be used to endorse or promote products derived from this software 18 | without specific prior written permission of Yahoo Inc. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 21 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 22 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 24 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 25 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 26 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 27 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 29 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /packages/fluxible-addons-react/LICENSE.md: -------------------------------------------------------------------------------- 1 | Software License Agreement (BSD License) 2 | ======================================== 3 | 4 | Copyright (c) 2015, Yahoo Inc. All rights reserved. 5 | ---------------------------------------------------- 6 | 7 | Redistribution and use of this software in source and binary forms, with or 8 | without modification, are permitted provided that the following conditions are 9 | met: 10 | 11 | * Redistributions of source code must retain the above copyright notice, this 12 | list of conditions and the following disclaimer. 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | this list of conditions and the following disclaimer in the documentation 15 | and/or other materials provided with the distribution. 16 | * Neither the name of Yahoo Inc. nor the names of this package's contributors 17 | may be used to endorse or promote products derived from this software 18 | without specific prior written permission of Yahoo Inc. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 21 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 22 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 24 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 25 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 26 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 27 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 29 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /packages/fluxible-reducer-store/LICENSE.md: -------------------------------------------------------------------------------- 1 | Software License Agreement (BSD License) 2 | ======================================== 3 | 4 | Copyright (c) 2015, Yahoo Inc. All rights reserved. 5 | ---------------------------------------------------- 6 | 7 | Redistribution and use of this software in source and binary forms, with or 8 | without modification, are permitted provided that the following conditions are 9 | met: 10 | 11 | * Redistributions of source code must retain the above copyright notice, this 12 | list of conditions and the following disclaimer. 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | this list of conditions and the following disclaimer in the documentation 15 | and/or other materials provided with the distribution. 16 | * Neither the name of Yahoo Inc. nor the names of this package's contributors 17 | may be used to endorse or promote products derived from this software 18 | without specific prior written permission of Yahoo Inc. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 21 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 22 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 24 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 25 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 26 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 27 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 29 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /packages/generator-fluxible/LICENSE.md: -------------------------------------------------------------------------------- 1 | Software License Agreement (BSD License) 2 | ======================================== 3 | 4 | Copyright (c) 2015, Yahoo Inc. All rights reserved. 5 | ---------------------------------------------------- 6 | 7 | Redistribution and use of this software in source and binary forms, with or 8 | without modification, are permitted provided that the following conditions are 9 | met: 10 | 11 | * Redistributions of source code must retain the above copyright notice, this 12 | list of conditions and the following disclaimer. 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | this list of conditions and the following disclaimer in the documentation 15 | and/or other materials provided with the distribution. 16 | * Neither the name of Yahoo Inc. nor the names of this package's contributors 17 | may be used to endorse or promote products derived from this software 18 | without specific prior written permission of Yahoo Inc. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 21 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 22 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 24 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 25 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 26 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 27 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 29 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /packages/fluxible-reducer-store/tests/fixtures/data/messages.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { 3 | id: 'm_1', 4 | threadID: 't_1', 5 | threadName: 'Jing and Bill', 6 | authorName: 'Bill', 7 | text: 'Hey Jing, want to give a Flux talk at ForwardJS?', 8 | timestamp: Date.now() - 99999 9 | }, 10 | { 11 | id: 'm_2', 12 | threadID: 't_1', 13 | threadName: 'Jing and Bill', 14 | authorName: 'Bill', 15 | text: 'Seems like a pretty cool conference.', 16 | timestamp: Date.now() - 89999 17 | }, 18 | { 19 | id: 'm_3', 20 | threadID: 't_1', 21 | threadName: 'Jing and Bill', 22 | authorName: 'Jing', 23 | text: 'Sounds good. Will they be serving dessert?', 24 | timestamp: Date.now() - 79999 25 | }, 26 | { 27 | id: 'm_4', 28 | threadID: 't_2', 29 | threadName: 'Dave and Bill', 30 | authorName: 'Bill', 31 | text: 'Hey Dave, want to get a beer after the conference?', 32 | timestamp: Date.now() - 69999 33 | }, 34 | { 35 | id: 'm_5', 36 | threadID: 't_2', 37 | threadName: 'Dave and Bill', 38 | authorName: 'Dave', 39 | text: 'Totally! Meet you at the hotel bar.', 40 | timestamp: Date.now() - 59999 41 | }, 42 | { 43 | id: 'm_6', 44 | threadID: 't_3', 45 | threadName: 'Functional Heads', 46 | authorName: 'Bill', 47 | text: 'Hey Brian, are you going to be talking about functional stuff?', 48 | timestamp: Date.now() - 49999 49 | }, 50 | { 51 | id: 'm_7', 52 | threadID: 't_3', 53 | threadName: 'Bill and Brian', 54 | authorName: 'Brian', 55 | text: 'At ForwardJS? Yeah, of course. See you there!', 56 | timestamp: Date.now() - 39999 57 | } 58 | ]; 59 | -------------------------------------------------------------------------------- /packages/fluxible/tests/fixtures/plugins/TestApplicationPluginSync.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2014, Yahoo! Inc. 3 | * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. 4 | */ 5 | 'use strict'; 6 | 7 | module.exports = function TestApplicationPlugin(initialFoo, initialBar) { 8 | var foo = initialFoo; 9 | var bar = initialBar; 10 | return { 11 | name: 'TestAppPlugin', 12 | plugContext: function (options) { 13 | return { 14 | plugActionContext: function plugActionContext(actionContext) { 15 | actionContext.getFoo = function () { 16 | return foo; 17 | }; 18 | actionContext.getBar = function () { 19 | return bar; 20 | }; 21 | }, 22 | plugComponentContext: function plugComponentContext(componentContext) { 23 | componentContext.getFoo = function () { 24 | return foo; 25 | }; 26 | }, 27 | plugStoreContext: function plugStoreContext(storeContext) { 28 | storeContext.getFoo = function () { 29 | return foo; 30 | }; 31 | }, 32 | dehydrate: function () { 33 | return { 34 | bar: bar 35 | }; 36 | }, 37 | rehydrate: function (state) { 38 | bar = state.bar; 39 | } 40 | }; 41 | }, 42 | dehydrate: function dehydrate() { 43 | return { 44 | foo: foo 45 | }; 46 | }, 47 | rehydrate: function rehydrate(state) { 48 | foo = state.foo; 49 | } 50 | } 51 | }; 52 | -------------------------------------------------------------------------------- /packages/fluxible-addons-react/docs/api/FluxibleMixin.md: -------------------------------------------------------------------------------- 1 | # FluxibleMixin 2 | 3 | ***FluxibleMixin will be deprecated since React mixin support will eventually 4 | be removed. It is highly recommended to use [provideContext](provideContext.md) 5 | instead.*** 6 | 7 | ```js 8 | var FluxibleMixin = require('fluxible-addons-react/FluxibleMixin'); 9 | ``` 10 | 11 | The mixin will add the `contextTypes` `getStore` and `executeAction` 12 | to your component. 13 | 14 | The mixin can also be used to statically list store dependencies and listen to 15 | them automatically in `componentDidMount`. This is done by adding a static 16 | property `storeListeners` in your component. 17 | 18 | You can do this with an array, which will default all store listeners to call 19 | the `onChange` method: 20 | 21 | ```js 22 | var FooStore = require('./stores/FooStore'); // Your store 23 | var Component = React.createClass({ 24 | mixins: [FluxibleMixin], 25 | statics: { 26 | storeListeners: [FooStore] 27 | }, 28 | onChange: function () { 29 | this.setState(this.getStore(FooStore).getState()); 30 | }, 31 | }); 32 | ``` 33 | 34 | Or you can be more explicit with which function to call for each store by using a hash: 35 | 36 | ```js 37 | var FooStore = require('./stores/FooStore'); // Your store 38 | var BarStore = require('./stores/BarStore'); // Your store 39 | var Component = React.createClass({ 40 | mixins: [FluxibleMixin], 41 | statics: { 42 | storeListeners: { 43 | onFooStoreChange: [FooStore], 44 | onBarStoreChange: [BarStore] 45 | } 46 | }, 47 | onFooStoreChange: function () { 48 | this.setState(this.getStore(FooStore).getState()); 49 | }, 50 | onBarStateChange: function () { 51 | this.setState(this.getStore(BarStore).getState()); 52 | } 53 | }); 54 | ``` 55 | 56 | This prevents boilerplate for listening to stores in `componentDidMount` and 57 | unlistening in `componentWillUnmount`. 58 | -------------------------------------------------------------------------------- /packages/fluxible/tests/fixtures/plugins/TestApplicationPlugin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2014, Yahoo! Inc. 3 | * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. 4 | */ 5 | 'use strict'; 6 | 7 | module.exports = function TestApplicationPlugin(initialFoo, initialBar) { 8 | var foo = initialFoo; 9 | var bar = initialBar; 10 | return { 11 | name: 'TestAppPlugin', 12 | plugContext: function (options) { 13 | return { 14 | plugActionContext: function plugActionContext(actionContext) { 15 | actionContext.getFoo = function () { 16 | return foo; 17 | }; 18 | actionContext.getBar = function () { 19 | return bar; 20 | }; 21 | }, 22 | plugComponentContext: function plugComponentContext(componentContext) { 23 | componentContext.getFoo = function () { 24 | return foo; 25 | }; 26 | }, 27 | plugStoreContext: function plugStoreContext(storeContext) { 28 | storeContext.getFoo = function () { 29 | return foo; 30 | }; 31 | }, 32 | dehydrate: function () { 33 | return { 34 | bar: bar 35 | }; 36 | }, 37 | rehydrate: function (state, done) { 38 | bar = state.bar; 39 | setImmediate(done); 40 | } 41 | }; 42 | }, 43 | dehydrate: function dehydrate() { 44 | return { 45 | foo: foo 46 | }; 47 | }, 48 | rehydrate: function rehydrate(state, done) { 49 | foo = state.foo; 50 | setImmediate(done); 51 | } 52 | } 53 | }; 54 | -------------------------------------------------------------------------------- /packages/fluxible/UPGRADE.md: -------------------------------------------------------------------------------- 1 | # Upgrade Guide 2 | 3 | ## `1.0.0` 4 | 5 | ### React addons removed from Fluxible 6 | 7 | All previously deprecated React integrations have been removed. See notes 8 | from `0.5.0` on migration to `fluxible-addons-react`. Additionally, 9 | `require('fluxible').contextTypes` has been removed and will be part of 10 | `fluxible-addons-react`. 11 | 12 | ### `componentActionHandler` renamed to `componentActionErrorHandler` 13 | 14 | This has been renamed to reflect that it is only called when an error occurs. 15 | 16 | ### Promise library dependency removed 17 | 18 | In keeping up-to-date with ES2015, the dependency on `es6-promise` and 19 | `object-assign` libraries have been removed. Users should now add their 20 | own polyfills to their application in browsers that do not support 21 | these features yet. We recommend using [Babel](https://babeljs.io/). 22 | 23 | ## `0.5.0` 24 | 25 | ### React addons moved to fluxible-addons-react 26 | 27 | The React addons have been broken out into a separate package called [fluxible-addons-react](https://github.com/yahoo/fluxible-addons-react). This was done to faciliate the use of Fluxible with other view libraries like React Native. 28 | 29 | Your application should depend on `fluxible-addons-react` directly and require the addons from that package. 30 | 31 | Note the new way to requre these with es6 uses curly braces: 32 | 33 | ```js 34 | import {connectToStores} from 'fluxible-addons-react'; 35 | ``` 36 | 37 | ### `connectToStores`'s `getStateFromStores` signature has changed to `(context, props)` 38 | 39 | Your `connectToStores` should change from: 40 | 41 | ```js 42 | connectToStores(Component, [MyStore], (stores, props) => { 43 | return { 44 | foo: stores.MyStore.getFoo() 45 | }; 46 | }); 47 | ``` 48 | 49 | to 50 | 51 | ```js 52 | connectToStores(Component, [MyStore], (context, props) => { 53 | return { 54 | foo: context.getStore(MyStore).getFoo() 55 | }; 56 | }); 57 | ``` 58 | -------------------------------------------------------------------------------- /packages/generator-fluxible/README.md: -------------------------------------------------------------------------------- 1 | # generator-fluxible 2 | 3 | [![npm version](https://badge.fury.io/js/generator-fluxible.svg)](http://badge.fury.io/js/generator-fluxible) 4 | 9 | 10 | ## Getting Started 11 | 12 | ```bash 13 | npm install -g yo 14 | ``` 15 | 16 | To install generator-fluxible from npm, run: 17 | 18 | ```bash 19 | npm install -g generator-fluxible 20 | ``` 21 | 22 | Finally, initiate the generator: 23 | 24 | ```bash 25 | yo fluxible 26 | ``` 27 | 28 | During development, execute `npm run dev` to initiate webpack-dev-server 29 | (with react-hot-loader support) and your application's server using nodemon. 30 | Browse to `http://localhost:3000` to see a very simple Fluxible site with 31 | server-side rendering and client-side navigation. When you change files, 32 | the client will be hot-reloaded (with the exception of stores) and your 33 | application server will restart so that you can see the server-side changes 34 | on the next refresh. 35 | 36 | For other environments, make sure your application is built using 37 | `npm run build` and then run `npm start`. 38 | 39 | ## Debugging 40 | 41 | Fluxible uses [debug](https://www.npmjs.com/package/debug) to expose debugging 42 | information on the server and client. 43 | 44 | ### Server 45 | 46 | Start the application with the `DEBUG` environment variable: `DEBUG=* grunt`. 47 | 48 | ### Client 49 | 50 | `fluxibleDebug` is exposed to the `window` object to manage debugging. You can 51 | enable it via the browser console: `fluxibleDebug.enable('*');` then refresh 52 | the page. To disable, type the following: `fluxibleDebug.disable();`. 53 | -------------------------------------------------------------------------------- /packages/fluxible/tests/fixtures/plugins/TestApplicationPluginPromise.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2014, Yahoo! Inc. 3 | * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. 4 | */ 5 | 'use strict'; 6 | 7 | module.exports = function TestApplicationPlugin(initialFoo, initialBar) { 8 | var foo = initialFoo; 9 | var bar = initialBar; 10 | return { 11 | name: 'TestAppPlugin', 12 | plugContext: function (options) { 13 | return { 14 | plugActionContext: function plugActionContext(actionContext) { 15 | actionContext.getFoo = function () { 16 | return foo; 17 | }; 18 | actionContext.getBar = function () { 19 | return bar; 20 | }; 21 | }, 22 | plugComponentContext: function plugComponentContext(componentContext) { 23 | componentContext.getFoo = function () { 24 | return foo; 25 | }; 26 | }, 27 | plugStoreContext: function plugStoreContext(storeContext) { 28 | storeContext.getFoo = function () { 29 | return foo; 30 | }; 31 | }, 32 | dehydrate: function () { 33 | return { 34 | bar: bar 35 | }; 36 | }, 37 | rehydrate: function (state, done) { 38 | return new Promise(function (resolve) { 39 | bar = state.bar; 40 | resolve(); 41 | }); 42 | } 43 | }; 44 | }, 45 | dehydrate: function dehydrate() { 46 | return { 47 | foo: foo 48 | }; 49 | }, 50 | rehydrate: function rehydrate(state) { 51 | return new Promise(function (resolve) { 52 | foo = state.foo; 53 | resolve(); 54 | }); 55 | } 56 | } 57 | }; 58 | -------------------------------------------------------------------------------- /packages/fluxible-addons-react/README.md: -------------------------------------------------------------------------------- 1 | # Fluxible Addons for React 2 | 3 | [![NPM version](https://badge.fury.io/js/fluxible-addons-react.svg)](http://badge.fury.io/js/fluxible-addons-react) 4 | 10 | 11 | [Fluxible](https://github.com/yahoo/fluxible) addons for use with [React](https://github.com/facebook/react). 12 | 13 | ```bash 14 | $ npm install --save fluxible-addons-react 15 | ``` 16 | 17 | Join the #fluxible channel of the [Reactiflux](http://reactiflux.com) Discord community. 18 | 19 | [![Gitter chat](https://badges.gitter.im/gitterHQ/gitter.png)](https://gitter.im/yahoo/fluxible) 20 | 21 | ## Docs 22 | 23 | * [connectToStores](https://github.com/yahoo/fluxible-addons-react/blob/master/docs/api/connectToStores.md) 24 | * [FluxibleComponent](https://github.com/yahoo/fluxible-addons-react/blob/master/docs/api/FluxibleComponent.md) 25 | * [FluxibleMixin](https://github.com/yahoo/fluxible-addons-react/blob/master/docs/api/FluxibleMixin.md) (legacy) 26 | * [provideContext](https://github.com/yahoo/fluxible-addons-react/blob/master/docs/api/provideContext.md) 27 | * [createElementWithContext](https://github.com/yahoo/fluxible-addons-react/blob/master/docs/api/createElementWithContext.md) 28 | * [batchedUpdatePlugin](https://github.com/yahoo/fluxible-addons-react/blob/master/docs/api/batchedUpdatePlugin.md) 29 | 30 | ## License 31 | 32 | This software is free to use under the Yahoo Inc. BSD license. 33 | See the [LICENSE file][] for license text and copyright information. 34 | 35 | [LICENSE file]: https://github.com/yahoo/fluxible/blob/master/LICENSE.md 36 | -------------------------------------------------------------------------------- /packages/fluxible/docs/api/README.md: -------------------------------------------------------------------------------- 1 | # API Documentation 2 | 3 | `var Fluxible = require('fluxible')` exports the following: 4 | 5 | * [`Fluxible`](Fluxible.md) 6 | 7 | 8 | or if you're using ES6: 9 | 10 | ```js 11 | import Fluxible from 'fluxible'; 12 | ``` 13 | 14 | While building your application, you will need to build [stores](Stores.md), [actions](Actions.md) and connect them to your [components](Components.md). 15 | 16 | Occasionally you may find that you need to use [plugins](Plugins.md) to extend Fluxible's interfaces. 17 | 18 | ## Addons 19 | 20 | `var FluxibleAddons = require('fluxible/addons')` also provides helper utilities for creating stores: 21 | 22 | * [`FluxibleAddons.BaseStore`](addons/BaseStore.md) 23 | * [`FluxibleAddons.createStore`](addons/createStore.md) 24 | 25 | or if you're using ES6: 26 | 27 | ```js 28 | import {BaseStore, createStore} from 'fluxible/addons'; 29 | ``` 30 | 31 | These libraries are not bundled with the main Fluxible export because stores are decoupled from Fluxible. 32 | 33 | ## React Addons 34 | 35 | `npm i --save fluxible-addons-react` 36 | 37 | `var ReactAddons = require('fluxible-addons-react');` provides helpers for 38 | using Fluxible with React. 39 | 40 | * [`FluxibleAddons.connectToStores`](../../../../packages/fluxible-addons-react/docs/api/connectToStores.md) 41 | * [`FluxibleAddons.FluxibleComponent`](../../../../packages/fluxible-addons-react/docs/api/FluxibleComponent.md) 42 | * [`FluxibleAddons.FluxibleMixin`](../../../../packages/fluxible-addons-react/docs/api/FluxibleMixin.md) 43 | * [`FluxibleAddons.provideContext`](../../../../packages/fluxible-addons-react/docs/api/provideContext.md) 44 | 45 | or if you're using ES6: 46 | 47 | ```js 48 | import {connectToStores, FluxibleComponent, FluxibleMixin, provideContext} from 'fluxible-addons-react'; 49 | ``` 50 | 51 | These libraries are not bundled with Fluxible to allow for Fluxible usage with 52 | other view libraries such as React Native. 53 | 54 | ## Utils 55 | 56 | Fluxible also provides the following utilities for testing your components: 57 | 58 | * [createMockActionContext](Actions.md#testing) 59 | * [createMockComponentContext](Components.md#testing) 60 | -------------------------------------------------------------------------------- /packages/fluxible-addons-react/docs/api/connectToStores.md: -------------------------------------------------------------------------------- 1 | # connectToStores 2 | 3 | ```js 4 | import connectToStores from 'fluxible-addons-react/connectToStores'; 5 | ``` 6 | 7 | `connectToStores` is a higher-order component that provides a convenient way to access state from the stores from within your component. It takes care of defining `getInitialState` and listening to the stores for updates. The store state will be sent to the `Component` instance as props. It is required that the React context is set and has access to `getStore`. It is recommended to use [`provideContext`](provideContext.md) around your top level component to do this for you. 8 | 9 | Takes the following parameters: 10 | 11 | * `Component` - the component that should receive the state as props, optional if using decorator pattern 12 | * `stores` - array of store constructors to listen for changes 13 | * `getStateFromStores` - function that receives all stores and should return the full state object. Receives `stores` hash and component `props` as arguments 14 | * `customContextTypes` (*optional*) - additional `contextTypes` that could be accessed from your `getStateFromStores` function 15 | 16 | ## Example 17 | 18 | The following example will listen to changes in `FooStore` and `BarStore` and pass `foo` and `bar` as props to the `Component` when it is instantiated. 19 | 20 | ```js 21 | class Component extends React.Component { 22 | render() { 23 | return ( 24 |
      25 |
    • {this.props.foo}
    • 26 |
    • {this.props.bar}
    • 27 |
    28 | ); 29 | } 30 | } 31 | 32 | Component = connectToStores(Component, [FooStore, BarStore], (context, props) => ({ 33 | foo: context.getStore(FooStore).getFoo(), 34 | bar: context.getStore(BarStore).getBar() 35 | })); 36 | 37 | export default Component; 38 | ``` 39 | 40 | ### Decorator 41 | 42 | You may also use the 43 | [decorator pattern](https://github.com/wycats/javascript-decorators): 44 | 45 | ```js 46 | @connectToStores([FooStore, BarStore], (context, props) => ({ 47 | foo: context.getStore(FooStore).getFoo(), 48 | bar: context.getStore(BarStore).getBar() 49 | })) 50 | class Component extends React.Component { 51 | render() { 52 | return
    ; 53 | } 54 | } 55 | export default Component; 56 | ``` 57 | -------------------------------------------------------------------------------- /docs/community/libraries.md: -------------------------------------------------------------------------------- 1 | # Libraries 2 | 3 | Take a look at some of the libraries that our community has built. Edit this page on GitHub to list your libraries or plugins. 4 | 5 | ## General 6 | 7 | * [fumble](https://github.com/yahoo/fumble) - Simple error objects in node. Created specifically to be used with the [fetchr](https://github.com/yahoo/fetchr) library and based on [hapi.js](http://hapijs.com/)' [Boom](https://github.com/hapijs/boom). 8 | 9 | * [fluxible-plugin-service-proxy](https://github.com/benbria/node-fluxible-plugin-service-proxy) - Fluxible plugin for registering services with different implementations on the server and the client. 10 | 11 | * [fluxible-plugin-facebook](https://github.com/Hairfie/fluxible-plugin-facebook) - Fluxible plugin for integrating the facebook's official SDK in fluxible applications. 12 | 13 | * [fluxible-plugin-cookie](https://github.com/Hairfie/fluxible-plugin-cookie) - Fluxible plugin for writing and reading cookies. 14 | 15 | * [fluxible-plugin-middleware](https://github.com/geekyme/fluxible-plugin-middleware) - Fluxible plugin for adding middlewares in Redux style! 16 | 17 | ## Data Services 18 | 19 | * [fluxible-plugin-fetchr](https://github.com/yahoo/fluxible-plugin-fetchr) - Isomorphic data fetching services. 20 | 21 | ## Routing 22 | 23 | * [fluxible-router](https://github.com/yahoo/fluxible-router) - Flux-based routing using Fluxible. 24 | * [react-router](https://github.com/rackt/react-router) - React-centric routing. 25 | 26 | ## Actions 27 | 28 | * [fluxible-action-utils](https://github.com/yahoo/fluxible-action-utils) - utility methods to aid in writing [actions](http://fluxible.io/api/fluxible-context.html#executeaction-action-payload-callback-) for [fluxible](http://fluxible.io) based applications. 29 | * [fluxible-plugin-actions](https://github.com/gingur/fluxible-plugin-actions) - component context utility to get actions by key instead of path, useful for cross library components. 30 | 31 | ## Immutable.js 32 | 33 | * [fluxible-immutable-store](https://www.npmjs.com/packages/fluxible-immutable-store) - A helper for creating Fluxible/Dispatchr BaseStores that manage immutable data structures. 34 | * [fluxible-immutable-utils](https://github.com/yahoo/fluxible-immutable-utils) - A mixin that provides a convenient interface for using Immutable.js inside react components. 35 | -------------------------------------------------------------------------------- /packages/fluxible/tests/unit/utils/createMockComponentContext.js: -------------------------------------------------------------------------------- 1 | /* jshint newcap:false */ 2 | /* global describe, it, before, after */ 3 | 4 | 'use strict'; 5 | 6 | var expect = require('chai').expect; 7 | var mockery = require('mockery'); 8 | var createMockComponentContext = require('../../../utils').createMockComponentContext; 9 | 10 | describe('createMockComponentContext', function () { 11 | 12 | describe('instance', function () { 13 | var context; 14 | 15 | before(function () { 16 | context = createMockComponentContext(); 17 | }); 18 | 19 | it('should have the following properties: dispatcher, executeActionCalls', function () { 20 | expect(context).to.have.property('executeActionCalls').that.is.an('array').and.empty; 21 | }); 22 | 23 | describe('#getStore', function () { 24 | it('should delegate to the dispatcher getStore method', function () { 25 | expect(context).to.have.property('getStore'); 26 | }); 27 | }); 28 | 29 | describe('#executeAction', function () { 30 | it('should provide an executeAction method', function () { 31 | expect(context).to.respondTo('executeAction'); 32 | }); 33 | 34 | it('should execute the action and pass a MockActionContext', function () { 35 | var mockPayload = { 36 | foo: 'bar', 37 | baz: 'fubar' 38 | }; 39 | 40 | function mockAction (ctx, payload, done) { 41 | expect(ctx).to.be.an('object'); 42 | expect(ctx.dispatch).to.be.a('function'); 43 | expect(ctx.executeAction).to.be.a('function'); 44 | expect(payload).to.equal(mockPayload); 45 | done(); 46 | } 47 | 48 | context.executeAction(mockAction, mockPayload); 49 | 50 | expect(context.executeActionCalls).to.have.length(1); 51 | 52 | var call = context.executeActionCalls[0]; 53 | expect(call).to.have.property('action', mockAction); 54 | expect(call).to.have.property('payload', mockPayload); 55 | }); 56 | }); 57 | }); 58 | 59 | after(function() { 60 | mockery.deregisterAll(); 61 | mockery.disable(); 62 | }); 63 | }); 64 | -------------------------------------------------------------------------------- /packages/fluxible-addons-react/docs/api/provideContext.md: -------------------------------------------------------------------------------- 1 | # provideContext 2 | 3 | ```js 4 | import provideContext from 'fluxible-addons-react/provideContext'; 5 | ``` 6 | 7 | `provideContext` wraps the `Component` with a higher-order component that 8 | specifies the child context for you. This allows the React context to propagate 9 | to all children that specify their `contextTypes`. 10 | 11 | Receives the following parameters: 12 | 13 | * `Component`: the component to wrap, typically your top-level application 14 | component, optional if using decorator pattern 15 | * `customContextTypes`: additional `childContextTypes` to add; useful for 16 | plugins that add to the component context 17 | 18 | By default, the `executeAction` and `getStore` methods will be added to the 19 | child context and `customContextTypes` will be merged with these defaults. 20 | 21 | ## Example 22 | 23 | The most typical and basic usage of `provideContext` is to wrap your 24 | Application component to ensure that it receives the `getStore` and 25 | `executeAction` methods. 26 | 27 | ```js 28 | class Application extends React.Component { 29 | render() { 30 | ... 31 | } 32 | } 33 | Application = provideContext(Application); 34 | export default Application; 35 | ``` 36 | 37 | Now when you render the Application component, you can pass in the component 38 | context via the `context` prop and be assured that all children will have 39 | access to it by specifying the respective `contextType`. 40 | 41 | ```js 42 | ReactDOM.renderToString(); 43 | ``` 44 | 45 | If you're using the 46 | [`createElementWithContext()` method](createElementWithContext.md) and you 47 | passed a higher-order `provideContext` component to the Fluxible constructor, 48 | then the `context` prop will automatically be passed in for you. 49 | 50 | ### Plugins and Custom Component Context 51 | 52 | Since plugins have the ability to add new methods or properties to the 53 | component context, you can specify custom `childContextTypes` through the 54 | second parameter. 55 | 56 | ```js 57 | Application = provideContext(Application, { 58 | foo: React.PropTypes.string 59 | }); 60 | ``` 61 | 62 | ### Decorator 63 | 64 | You may also use the 65 | [decorator pattern](https://github.com/wycats/javascript-decorators): 66 | 67 | ```js 68 | @provideContext({ 69 | foo: React.PropTypes.string 70 | }) 71 | class Application extends React.Component { 72 | render() { 73 | ... 74 | } 75 | } 76 | export default Application; 77 | ``` 78 | -------------------------------------------------------------------------------- /packages/fluxible-reducer-store/tests/fixtures/stores/MessageStore.js: -------------------------------------------------------------------------------- 1 | import {createReducerStore} from '../../../index'; 2 | 3 | const reducers = { 4 | RECEIVE_MESSAGES: (state, messages) => { 5 | var oldMessages = state.messages; 6 | var newMessages = {...oldMessages}; 7 | messages.forEach(function (message) { 8 | newMessages[message.id] = { 9 | ...message, 10 | isRead: false 11 | }; 12 | }); 13 | var sortedByDate = (newMessages && Object.keys(newMessages)) || []; 14 | sortedByDate.sort(function (a, b) { 15 | if (newMessages[a].date < newMessages[b].date) { 16 | return -1; 17 | } else if (newMessages[a].date > newMessages[b].date) { 18 | return 1; 19 | } 20 | return 0; 21 | }); 22 | 23 | return { 24 | messages: newMessages, 25 | sortedByDate 26 | }; 27 | }, 28 | OPEN_THREAD: (state, payload) => { 29 | // Mark all read 30 | var oldMessages = state.messages; 31 | var newMessages = { 32 | ...oldMessages 33 | }; 34 | Object.keys(state.messages).forEach((key) => { 35 | var message = state.messages[key]; 36 | if (message.threadID === payload.threadID) { 37 | newMessages[key] = { 38 | ...message, 39 | text: message.text + 'foo', 40 | isRead: true 41 | }; 42 | } 43 | }); 44 | return { 45 | ...state, 46 | messages: newMessages 47 | }; 48 | } 49 | }; 50 | 51 | var getters = { 52 | getAll: function getAll(state) { 53 | return state.messages; 54 | }, 55 | get: function get(state, id) { 56 | return state.messages[id]; 57 | }, 58 | getAllForThread: function getAllForThread(state, threadID) { 59 | var threadMessages = []; 60 | state.sortedByDate.forEach(function (key) { 61 | var message = state.messages[key]; 62 | if (message.threadID === threadID) { 63 | threadMessages.push(message); 64 | } 65 | }); 66 | return threadMessages; 67 | } 68 | }; 69 | 70 | var MessageStore = createReducerStore({ 71 | storeName: 'MessageStore', 72 | initialState: { 73 | messages: {}, 74 | sortedByDate: [] 75 | }, 76 | reducers: reducers, 77 | getters: getters 78 | }); 79 | 80 | export default MessageStore; 81 | -------------------------------------------------------------------------------- /packages/generator-fluxible/app/templates/server.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This leverages Express to create and run the http server. 3 | * A Fluxible context is created and executes the navigateAction 4 | * based on the URL. Once completed, the store state is dehydrated 5 | * and the application is rendered via React. 6 | */ 7 | 8 | import express from 'express'; 9 | import compression from 'compression'; 10 | import bodyParser from 'body-parser'; 11 | import path from 'path'; 12 | import serialize from 'serialize-javascript'; 13 | import {navigateAction} from 'fluxible-router'; 14 | import debugLib from 'debug'; 15 | import React from 'react'; 16 | import ReactDOM from 'react-dom/server'; 17 | import app from './app'; 18 | import HtmlComponent from './components/Html'; 19 | import { createElementWithContext } from 'fluxible-addons-react'; 20 | const env = process.env.NODE_ENV; 21 | 22 | const debug = debugLib('<%= name %>'); 23 | 24 | const server = express(); 25 | server.use('/public', express.static(path.join(__dirname, '/build'))); 26 | server.use(compression()); 27 | server.use(bodyParser.json()); 28 | 29 | server.use((req, res, next) => { 30 | const context = app.createContext(); 31 | 32 | debug('Executing navigate action'); 33 | context.getActionContext().executeAction(navigateAction, { 34 | url: req.url 35 | }, (err) => { 36 | if (err) { 37 | if (err.statusCode && err.statusCode === 404) { 38 | // Pass through to next middleware 39 | next(); 40 | } else { 41 | next(err); 42 | } 43 | return; 44 | } 45 | 46 | debug('Exposing context state'); 47 | const exposed = 'window.App=' + serialize(app.dehydrate(context)) + ';'; 48 | 49 | debug('Rendering Application component into html'); 50 | const markup = ReactDOM.renderToString(createElementWithContext(context)); 51 | const htmlElement = React.createElement(HtmlComponent, { 52 | clientFile: env === 'production' ? 'main.min.js' : 'main.js', 53 | context: context.getComponentContext(), 54 | state: exposed, 55 | markup: markup 56 | }); 57 | const html = ReactDOM.renderToStaticMarkup(htmlElement); 58 | 59 | debug('Sending markup'); 60 | res.type('html'); 61 | res.write('' + html); 62 | res.end(); 63 | }); 64 | }); 65 | 66 | const port = process.env.PORT || 3000; 67 | server.listen(port); 68 | console.log('Application listening on port ' + port); 69 | 70 | export default server; 71 | -------------------------------------------------------------------------------- /packages/fluxible-reducer-store/tests/unit/lib/createReducerStore.js: -------------------------------------------------------------------------------- 1 | /*global describe,it,beforeEach */ 2 | 3 | import {expect} from 'chai'; 4 | import MessageStore from '../../fixtures/stores/MessageStore'; 5 | import SingleReducerStore from '../../fixtures/stores/SingleReducerStore'; 6 | import messages from '../../fixtures/data/messages'; 7 | import {createMockActionContext} from 'fluxible/utils'; 8 | 9 | describe('fluxible-reducer-store', () => { 10 | var context; 11 | beforeEach(() => { 12 | context = createMockActionContext({ 13 | stores: [MessageStore, SingleReducerStore] 14 | }); 15 | }); 16 | it('should receive messages', () => { 17 | const messageStore = context.getStore(MessageStore); 18 | const singleStore = context.getStore(SingleReducerStore); 19 | expect(messageStore).to.be.instanceof(MessageStore); 20 | context.dispatch('RECEIVE_MESSAGES', messages); 21 | const threadMessages = messageStore.getAllForThread('t_1'); 22 | expect(singleStore.getState().count).to.equal(7); 23 | expect(threadMessages).to.be.an('array'); 24 | expect(threadMessages.length).to.equal(3); 25 | }); 26 | it('should handle open thread', () => { 27 | const store = context.getStore(MessageStore); 28 | const singleStore = context.getStore(SingleReducerStore); 29 | context.dispatch('RECEIVE_MESSAGES', messages); 30 | const threadMessages = store.getAllForThread('t_1'); 31 | threadMessages.forEach((message) => { 32 | expect(message.isRead).to.equal(false); 33 | }); 34 | context.dispatch('OPEN_THREAD', { 35 | threadID: 't_1' 36 | }); 37 | expect(singleStore.getState().count).to.equal(7); 38 | const newThreadMessages = store.getAllForThread('t_1'); 39 | newThreadMessages.forEach((message) => { 40 | expect(message.isRead).to.equal(true); 41 | }); 42 | }); 43 | it('should dehydrate correctly', () => { 44 | const store = context.getStore(MessageStore); 45 | context.dispatch('RECEIVE_MESSAGES', messages); 46 | const data = store.dehydrate(); 47 | expect(data).to.deep.equal(store.getState()); 48 | }); 49 | it('should rehydrate correctly', () => { 50 | const store = context.getStore(MessageStore); 51 | context.dispatch('RECEIVE_MESSAGES', messages); 52 | const data = store.dehydrate(); 53 | const beforeThreadMessages = store.getAllForThread('t_1'); 54 | const newStore = new MessageStore(); 55 | newStore.rehydrate(data); 56 | const afterThreadMessages = newStore.getAllForThread('t_1'); 57 | expect(afterThreadMessages).to.deep.equal(beforeThreadMessages); 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /docs/faq.md: -------------------------------------------------------------------------------- 1 | # FAQ 2 | 3 | Frequently asked questions from the community. 4 | 5 | 6 | ## General 7 | 8 | ### What are the drawbacks of using Fluxible? In contrast to Facebook's Flux library? 9 | 10 | It depends on whether you are building a server-side app. Facebook’s Flux only works on the client, but it may be easier to figure out since it doesn’t have to deal with concurrency issues. 11 | 12 | ### Is there a way to render a reactComponent only client side? 13 | 14 | Use the `componentDidMount` lifecycle event to set an internal state that re-renders itself on the client (i.e, `this.state.mounted`). 15 | 16 | 17 | 18 | 19 | ## Actions 20 | 21 | ### Can I fire the navigation function manually? 22 | 23 | Yes, you can call the [`navigate` action](https://github.com/yahoo/flux-router-component/blob/master/actions/navigate.js) from flux-router-component directly. 24 | 25 | 26 | ## Components 27 | 28 | ### Should `getStore` and `executeAction` be required in the `contextTypes` of `FluxibleMixin`? 29 | 30 | It is not required because we still support passing `context` as a prop to all of the components that need it. This is how things were done before we started using `context`. Some people still prefer this method since React’s `context` is not guaranteed to stay the way it is right now. 31 | 32 | 33 | ## Stores 34 | 35 | ### Can I call an action from a store? 36 | 37 | Right now, it is not possible. This will require some thought on how to ensure that the server can keep track of actions that are executed from stores. 38 | 39 | 40 | 41 | 42 | ## Data Fetching 43 | 44 | ### What does the error message "invalid csrf token" from Fetchr mean? 45 | 46 | This is caused by the csrf middleware. You need to make sure you pass the `csrf` token to the `createContext` method on the server. Check the server.js of [chat](https://github.com/yahoo/flux-examples/blob/master/chat/server.js#L37) or [todo](https://github.com/yahoo/flux-examples/blob/master/todo/server.js#L41) examples. 47 | 48 | 49 | 50 | 51 | ## Routing 52 | 53 | ### Which should I use, `react-router` or `flux-router-component`? 54 | 55 | [react-router](https://github.com/rackt/react-router) has some features that flux-router-component does not have, like nested routes, component `willTransitionTo` hooks, and built-in re-directs. If you feel like you do not need them, then you will be fine with flux-router-component. We prefer the flux-like flow, so we use flux-router-component internally. 56 | 57 | 58 | ### In react-router, how do I get access to the Fluxible context from `willTransitionTo`? 59 | 60 | Since `willTransitionTo` is defined statically on the component, it will not have access to the Fluxible context. There is a [open issue](https://github.com/rackt/react-router/pull/590) from react-router to provide access but, as of this writing) it has yet to be resolved. 61 | -------------------------------------------------------------------------------- /packages/fluxible-addons-react/provideContext.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015, Yahoo Inc. 3 | * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. 4 | */ 5 | 'use strict'; 6 | 7 | var React = require('react'); 8 | var hoistNonReactStatics = require('hoist-non-react-statics'); 9 | 10 | function createComponent(Component, customContextTypes) { 11 | var componentName = Component.displayName || Component.name; 12 | var childContextTypes = Object.assign({ 13 | executeAction: React.PropTypes.func.isRequired, 14 | getStore: React.PropTypes.func.isRequired 15 | }, customContextTypes || {}); 16 | 17 | var ContextProvider = React.createClass({ 18 | displayName: componentName + 'ContextProvider', 19 | 20 | propTypes: { 21 | context: React.PropTypes.object.isRequired 22 | }, 23 | 24 | childContextTypes: childContextTypes, 25 | 26 | getChildContext: function () { 27 | var childContext = { 28 | executeAction: this.props.context.executeAction, 29 | getStore: this.props.context.getStore 30 | }; 31 | if (customContextTypes) { 32 | Object.keys(customContextTypes).forEach(function (key) { 33 | childContext[key] = this.props.context[key]; 34 | }, this); 35 | } 36 | return childContext; 37 | }, 38 | 39 | render: function () { 40 | return React.createElement(Component, Object.assign({}, this.props, { ref: 'wrappedElement' })); 41 | } 42 | }); 43 | 44 | hoistNonReactStatics(ContextProvider, Component); 45 | 46 | return ContextProvider; 47 | } 48 | 49 | /** 50 | * Provides context prop to all children as React context 51 | * 52 | * Example: 53 | * var WrappedComponent = provideContext(Component, { 54 | * foo: React.PropTypes.string 55 | * }); 56 | * 57 | * Also supports the decorator pattern: 58 | * @provideContext({ 59 | * foo: React.PropTypes.string 60 | * }) 61 | * class ConnectedComponent extends React.Component { 62 | * render() { 63 | * return
    ; 64 | * } 65 | * } 66 | * 67 | * @method provideContext 68 | * @param {React.Component} [Component] component to wrap 69 | * @param {object} customContextTypes Custom contextTypes to add 70 | * @returns {React.Component} or {Function} if using decorator pattern 71 | */ 72 | module.exports = function provideContext(Component, customContextTypes) { 73 | // support decorator pattern 74 | if (arguments.length === 0 || typeof arguments[0] !== 'function') { 75 | customContextTypes = arguments[0]; 76 | return function connectToStoresDecorator(ComponentToDecorate) { 77 | return createComponent(ComponentToDecorate, customContextTypes); 78 | }; 79 | } 80 | 81 | return createComponent.apply(null, arguments); 82 | }; 83 | -------------------------------------------------------------------------------- /packages/generator-fluxible/app/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015, Yahoo! Inc. 3 | * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. 4 | */ 5 | 6 | 'use strict'; 7 | var yeoman = require('yeoman-generator'); 8 | var chalk = require('chalk'); 9 | var yosay = require('yosay'); 10 | var str = require('underscore.string'); 11 | 12 | module.exports = yeoman.generators.Base.extend({ 13 | initializing: function () { 14 | this.pkg = require('../package.json'); 15 | }, 16 | 17 | prompting: function () { 18 | var done = this.async(); 19 | 20 | this.log(yosay( 21 | 'Welcome to the riveting ' + chalk.red('Fluxible') + ' generator!' 22 | )); 23 | 24 | var prompts = [{ 25 | type: 'input', 26 | name: 'name', 27 | default: this.appname, 28 | message: 'Project name:', 29 | validate: function(input) { 30 | return !!input; 31 | } 32 | }]; 33 | 34 | this.prompt(prompts, function (props) { 35 | this.displayName = props.name; 36 | this.name = str.slugify(props.name); 37 | this.buildSystem = str.slugify(props.buildSystem); 38 | done(); 39 | }.bind(this)); 40 | }, 41 | 42 | writing: { 43 | config: function () { 44 | this.template('.babelrc', '.babelrc', this.context); 45 | this.template('.editorconfig', '.editorconfig', this.context); 46 | // .gitignore is renamed by npm to .npmignore, so use underscore 47 | this.template('_gitignore', '.gitignore', this.context); 48 | this.template('.eslintrc', '.eslintrc', this.context); 49 | this.template('package.json', 'package.json', this.context); 50 | }, 51 | 52 | projectfiles: function () { 53 | this.template('app.js', 'app.js', this.context); 54 | this.template('client.js', 'client.js', this.context); 55 | this.template('server.js', 'server.js', this.context); 56 | this.template('start.js', 'start.js', this.context); 57 | this.template('webpack.config.js', 'webpack.config.js', this.context); 58 | this.template('webpack.config.production.js', 'webpack.config.production.js', this.context); 59 | this.template('webpack-dev-server.js', 'webpack-dev-server.js', this.context); 60 | this.directory('actions', 'actions', this.context); 61 | this.directory('components', 'components', this.context); 62 | this.directory('configs', 'configs', this.context); 63 | this.directory('stores', 'stores', this.context); 64 | // Webpack dev server needs this folder to exist before starting 65 | this.template('_buildgitignore', 'build/js/.gitignore', this.context); 66 | } 67 | }, 68 | 69 | install: function () { 70 | this.installDependencies({ 71 | npm: true, 72 | bower: false, 73 | skipInstall: this.options['skip-install'] 74 | }); 75 | } 76 | }); 77 | -------------------------------------------------------------------------------- /packages/fluxible-addons-react/tests/unit/lib/FluxibleComponent.js: -------------------------------------------------------------------------------- 1 | /*globals describe,it,afterEach,beforeEach*/ 2 | 'use strict'; 3 | 4 | var expect = require('chai').expect; 5 | var mockery = require('mockery'); 6 | var React; 7 | var ReactDOM; 8 | var ReactTestUtils; 9 | var connectToStores; 10 | var provideContext; 11 | var FluxibleComponent; 12 | var FooStore = require('../../fixtures/stores/FooStore'); 13 | var BarStore = require('../../fixtures/stores/BarStore'); 14 | var createMockComponentContext = require('fluxible/utils/createMockComponentContext'); 15 | var jsdom = require('jsdom'); 16 | 17 | describe('fluxible-addons-react', function () { 18 | describe('FluxibleComponent', function () { 19 | var context; 20 | 21 | beforeEach(function (done) { 22 | jsdom.env('', [], function (err, window) { 23 | if (err) { 24 | done(err); 25 | } 26 | global.window = window; 27 | global.document = window.document; 28 | global.navigator = window.navigator; 29 | 30 | context = createMockComponentContext({ 31 | stores: [FooStore, BarStore] 32 | }); 33 | 34 | mockery.enable({ 35 | warnOnReplace: false, 36 | warnOnUnregistered: false, 37 | useCleanCache: true 38 | }); 39 | React = require('react'); 40 | ReactDOM = require('react-dom'); 41 | ReactTestUtils = require('react-addons-test-utils'); 42 | connectToStores = require('../../../').connectToStores; 43 | provideContext = require('../../../').provideContext; 44 | FluxibleComponent = require('../../../').FluxibleComponent; 45 | 46 | done(); 47 | }); 48 | }); 49 | 50 | afterEach(function () { 51 | delete global.window; 52 | delete global.document; 53 | delete global.navigator; 54 | mockery.disable(); 55 | }); 56 | 57 | it('will not double render', function () { 58 | const Component = React.createClass({ 59 | render: function () { 60 | return ( 61 |
    {this.props.children}
    62 | ); 63 | } 64 | }); 65 | const element = ReactTestUtils.renderIntoDocument( 66 | 67 | Some child 68 | 69 | ); 70 | expect(ReactDOM.findDOMNode(element).outerHTML).to.equal( 71 | '
    Some child
    ' 72 | ); 73 | }); 74 | 75 | it('should pass context prop to child', function (done) { 76 | const Component = React.createClass({ 77 | render: function () { 78 | expect(this.props.context).to.equal(context); 79 | done(); 80 | return ( 81 |
    {this.props.children}
    82 | ); 83 | } 84 | }); 85 | const element = ReactTestUtils.renderIntoDocument( 86 | 87 | Some child 88 | 89 | ); 90 | }); 91 | }); 92 | }); 93 | -------------------------------------------------------------------------------- /docs/community/reference-applications.md: -------------------------------------------------------------------------------- 1 | # Reference Applications 2 | 3 | Want to add your application? Click edit above and open a PR! 4 | 5 | ## Open Source 6 | 7 | - [Contactor](https://github.com/localnerve/flux-react-example) ([live link](http://flux-react-example.herokuapp.com)) is a data-driven Fluxible demo application. Uses [Fetchr](https://github.com/yahoo/fetchr), AMQP, and Github as a simple data service. Another possible useful source of inspiration for solving problems encountered while developing an isomorphic application. 8 | - [Dockunit.io](https://github.com/tlovett1/dockunit-platform/) ([live link](http://dockunit.io)) is a containerized continuous integration testing service for running [Dockunit](https://github.com/tlovett1/dockunit) test suites hosted on Github. Built on Fluxible and Docker. It is 100% open source. 9 | - [Flux Examples](https://github.com/yahoo/flux-examples) contains various Flux examples built with Fluxible. 10 | - [Fluxible.io](https://github.com/yahoo/fluxible.io) is this website! Showcases data fetching, routing, analytics, search and more. 11 | - [isomorphic500](https://github.com/gpbl/isomorphic500) ([live link](https://isomorphic500.herokuapp.com/)) is a Fluxible demo application showing photos from 500px. It uses the official Fluxible plugins, supports multiple language and includes a full-stack development environment. Useful as a source of inspiration to solve some common cases encountered while developing an isomorphic application. 12 | - [quad-blog](https://github.com/cesarandreu/quad-blog/) ([live link](https://blog.cesarandreu.com/)) is a blog written with Fluxible. Another possible useful source of inspiration for solving problems encountered while developing an isomorphic application. 13 | 14 | ## Closed Source 15 | 16 | - [6play.fr (Beta)](http://beta.6play.fr) is the video site for the french television Channel "M6" 17 | - [CodePad](https://codepad.remoteinterview.io) is an All-in-one pair programming tool ideal for phone interviews. Supports and compiles 10+ languages and has full bash access too. 18 | - [DICE FM](https://dice.fm) is a music startup based in London that handpicks the best gigs in your city. 19 | - [Docker Hub (Beta)](https://hub-beta.docker.com/) is a cloud-based platform service for distributed applications. It includes container image distribution and change management, user and team collaboration, and lifecycle workflow automation. 20 | - [LD-R](http://ld-r.org) Linked Data-driven Web Components. 21 | - [Mailjet Passport](https://www.mailjet.com/passport) is an email builder app (WYSIWYG & HTML Editor), automagically generating responsive emails. 22 | - [Porch](https://porch.com/) is the home network, helping homeowners make informed decisions on home improvements and in selecting the right home professionals. 23 | - [ShaderFrog](http://shaderfrog.com) is a WebGL, Three.js, Fluxible browser-based shader editor that can export GLSL shaders to multiple targets. 24 | - [SVT Sport](http://www.svt.se/sport/) is a isomorphic site providing sports coverage for Swedish Television. 25 | - [Yahoo Beauty](https://www.yahoo.com/beauty) is Yahoo's first digital magazine on Fluxible. 26 | - International Magazines: 27 | - [https://uk.style.yahoo.com/](https://uk.style.yahoo.com/) 28 | - [https://fr.people.yahoo.com/](https://fr.people.yahoo.com/) 29 | - [Yahoo Daily Fantasy](https://sports.yahoo.com/dailyfantasy) is a fantasy sports app where users can compete everyday with one-day or weeklong contests, setting lineups with simplified salary caps and easy access to news and scores from Yahoo’s Fantasy experts. 30 | - [Yahoo NFL Stream](https://nflstream.yahoo.com/) is the live video streaming site for the NFL on Yahoo. 31 | - [Yahoo Search SDK](https://developer.yahoo.com/search-sdk/apps/) is a software developer toolkit to integrate Yahoo Search into native apps. 32 | - [Yahoo Taiwan](https://tw.mobi.yahoo.com/) is the mobile frontpage for Yahoo Taiwan. 33 | -------------------------------------------------------------------------------- /gulpfile.babel.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import gulp from 'gulp'; 4 | import {cd, exec, rm} from 'shelljs'; 5 | import {argv} from 'yargs'; 6 | 7 | const ROOT_PATH = path.resolve(__dirname); 8 | const PACKAGES_PATH = path.resolve(__dirname, './packages'); 9 | const packages = fs.readdirSync(PACKAGES_PATH).filter((file) => { 10 | return fs.statSync(path.resolve(PACKAGES_PATH, file)).isDirectory(); 11 | }).reduce((acc, file) => { 12 | return { 13 | ...acc, 14 | [file]: path.resolve(PACKAGES_PATH, file) 15 | }; 16 | }, {}); 17 | 18 | const sharedDeps = [ 19 | 'react', 20 | 'react-dom' 21 | ]; 22 | gulp.task('install', () => { 23 | return Promise.all( 24 | // Link all packages to the root 25 | Object.keys(packages).map((packageName) => { 26 | return new Promise((resolve) => { 27 | cd(packages[packageName]); 28 | exec('npm link'); 29 | cd(ROOT_PATH); 30 | exec('npm link ' + packageName); 31 | resolve(); 32 | }); 33 | }) 34 | ).then(() => { 35 | // Remove duplicated packages and shared dependencies so they are loaded 36 | // from the top 37 | return Promise.all( 38 | Object.keys(packages).map((packageName) => { 39 | return Promise.all( 40 | Object.keys(packages).concat(sharedDeps).map((dependencyName) => { 41 | return new Promise((resolve) => { 42 | rm('-rf', path.resolve(packages[packageName], 'node_modules', dependencyName)); 43 | resolve(); 44 | }); 45 | }) 46 | ); 47 | }) 48 | ); 49 | }); 50 | }); 51 | 52 | gulp.task('clean', () => { 53 | // Remove package node_modules 54 | return Promise.all( 55 | Object.keys(packages).map((packageName) => { 56 | return new Promise((resolve) => { 57 | rm('-rf', path.resolve(packages[packageName], 'node_modules')); 58 | resolve(); 59 | }); 60 | }) 61 | ); 62 | }); 63 | 64 | gulp.task('version', () => { 65 | // Try to derive package name from directory where this was run from 66 | let pwd = process.env.PWD; 67 | let pwdPackageName = Object.keys(packages).reduce((prev, name) => { 68 | return packages[name] === pwd ? name : prev; 69 | }, undefined); 70 | 71 | // Check params 72 | let packageName = argv.pkg || argv.p || pwdPackageName; 73 | let version = argv.version || argv.v; 74 | if (!packageName || !version) { 75 | throw new Error('Usage: gulp version -p -v '); 76 | } 77 | 78 | // Bump the version 79 | cd(packages[packageName]); 80 | let execResult = exec('npm version ' + version); 81 | let bumpedVersion = execResult.output.replace('\n', '').replace('v', ''); 82 | 83 | // Commit and tag 84 | exec('git add ' + packages[packageName] + '/package.json'); 85 | let message = packageName + '@' + bumpedVersion; 86 | exec('git commit -m "' + message + '"'); 87 | let tagName = packageName + '-v' + bumpedVersion; 88 | exec('git tag ' + tagName); 89 | }); 90 | 91 | gulp.task('publish', () => { 92 | // Try to derive package name from directory where this was run from 93 | let pwd = process.env.PWD; 94 | let pwdPackageName = Object.keys(packages).reduce((prev, name) => { 95 | return packages[name] === pwd ? name : prev; 96 | }, undefined); 97 | 98 | // Check params 99 | let packageName = argv.pkg || argv.p || pwdPackageName; 100 | if (!packageName) { 101 | throw new Error('Usage: gulp publish -p '); 102 | } 103 | 104 | // Publish 105 | cd(packages[packageName]); 106 | exec('npm publish'); 107 | }); 108 | 109 | -------------------------------------------------------------------------------- /packages/fluxible/tests/unit/utils/createMockActionContext.js: -------------------------------------------------------------------------------- 1 | /* jshint newcap:false */ 2 | /* global describe, it, beforeEach, after */ 3 | 4 | 'use strict'; 5 | 6 | var expect = require('chai').expect; 7 | var mockery = require('mockery'); 8 | var createMockActionContext = require('../../../utils').createMockActionContext; 9 | 10 | describe('createMockActionContext', function () { 11 | 12 | describe('instance', function () { 13 | var context; 14 | 15 | beforeEach(function () { 16 | context = createMockActionContext(); 17 | }); 18 | 19 | it('should have executeActionCalls property', function () { 20 | expect(context).to.have.property('executeActionCalls').that.is.an('array').and.empty; 21 | }); 22 | 23 | it('should have dispatchCalls property', function () { 24 | expect(context).to.have.property('dispatchCalls').that.is.an('array').and.empty; 25 | }); 26 | 27 | it('should have dispatcherContext property', function () { 28 | expect(context).to.have.property('dispatcherContext').that.is.an('object'); 29 | }); 30 | 31 | describe('#getStore', function () { 32 | it('should delegate to the dispatcher getStore method', function () { 33 | expect(context).to.have.property('getStore'); 34 | }); 35 | }); 36 | 37 | describe('#executeAction', function () { 38 | var mockPayload = { 39 | foo: 'bar', 40 | baz: 'fubar' 41 | }; 42 | 43 | function mockAction (ctx, payload, cb) { 44 | expect(ctx).to.be.an('object'); 45 | expect(ctx.dispatch).to.be.a('function'); 46 | expect(ctx.executeAction).to.be.a('function'); 47 | expect(payload).to.equal(mockPayload); 48 | return cb(); 49 | } 50 | 51 | function cbResult (done) { 52 | return function cb () { 53 | expect(context.executeActionCalls).to.have.length(1); 54 | 55 | var call = context.executeActionCalls[0]; 56 | expect(call).to.have.property('action', mockAction); 57 | expect(call).to.have.property('payload', mockPayload); 58 | return done(); 59 | } 60 | } 61 | 62 | it('should provide an executeAction method', function () { 63 | expect(context).to.respondTo('executeAction'); 64 | }); 65 | 66 | it('should execute the action and infer to return a Promise, success', function (done) { 67 | context.executeAction(mockAction, mockPayload).then(cbResult(done)); 68 | }); 69 | 70 | it('should execute the action and infer to return a Promise, failure', function (done) { 71 | function mockActionFailure (ctx, payload, cb) { 72 | cb(new Error('mock')); 73 | } 74 | 75 | context.executeAction(mockActionFailure, mockPayload).then(function() { 76 | done(new Error('should not have resolved successfully')); 77 | }).catch(function (error) { 78 | expect(error).to.be.an('Error'); 79 | done(); 80 | }); 81 | }); 82 | 83 | it('should execute the action and infer to NOT return a Promise', function () { 84 | var returnValue = 'mock'; 85 | var result = context.executeAction(mockAction, mockPayload, cbResult(function () { 86 | return returnValue; 87 | })); 88 | expect(result).to.equal(returnValue); 89 | }); 90 | }); 91 | 92 | describe.skip('#dispatch', function () { 93 | }); 94 | }); 95 | 96 | after(function() { 97 | mockery.deregisterAll(); 98 | mockery.disable(); 99 | }); 100 | }); 101 | -------------------------------------------------------------------------------- /packages/fluxible-reducer-store/lib/createReducerStore.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015, Yahoo! Inc. 3 | * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. 4 | */ 5 | 'use strict'; 6 | var createStore = require('fluxible/addons').createStore; 7 | var __DEV__ = process.env.NODE_ENV !== 'production'; 8 | 9 | function stateIdentity(state) { return state; } 10 | 11 | module.exports = function createReducerStore(spec) { 12 | spec = spec || {}; 13 | var ReducerStore = createStore({ 14 | storeName: spec.storeName, 15 | handlers: {}, 16 | initialize: function () { 17 | this.state = spec.initialState; 18 | }, 19 | getState: function () { 20 | return this.state; 21 | }, 22 | dehydrate: function () { 23 | return (spec.dehydrate || stateIdentity)(this.state); 24 | }, 25 | rehydrate: function (state) { 26 | this.state = (spec.rehydrate || stateIdentity)(state); 27 | } 28 | }); 29 | 30 | function replaceReducers(reducers) { 31 | spec.reducers = reducers || {}; 32 | // Allow a single reducer function 33 | if (spec.reducer) { 34 | spec.reducers = { 35 | 'default': spec.reducer 36 | }; 37 | } 38 | // Create a handler proxy for each reducer so that the store can 39 | // still be lazy instantiated by dispatchr. 40 | Object.keys(spec.reducers).forEach(function eachReducer(eventName) { 41 | var reducer = spec.reducers[eventName]; 42 | if (!ReducerStore.handlers[eventName]) { 43 | ReducerStore.handlers[eventName] = function (payload, dispatchedEventName) { 44 | if (__DEV__) { 45 | if (!spec.reducers[eventName]) { 46 | // Calling a reducer that has been removed should be 47 | // non-fatal, but warning may help with bugs. 48 | console.warn(eventName + ' reducer on ' + spec.storeName + 49 | ' has been removed.'); 50 | return; 51 | } 52 | } 53 | var startingState = this.state; 54 | this.state = spec.reducers[eventName](this.state, payload, dispatchedEventName); 55 | if (this.state !== startingState) { 56 | this.emitChange(); 57 | } 58 | }; 59 | } 60 | }); 61 | } 62 | ReducerStore.replaceReducer = replaceReducers; 63 | ReducerStore.replaceReducers = replaceReducers; 64 | 65 | ReducerStore.replaceGetters = function replaceGetters(getters) { 66 | spec.getters = getters || {}; 67 | Object.keys(spec.getters).forEach(function (methodName) { 68 | if (!ReducerStore.prototype[methodName]) { 69 | ReducerStore.prototype[methodName] = function () { 70 | if (__DEV__) { 71 | if (!spec.getters[methodName]) { 72 | // Calling a method that has been removed would be 73 | // fatal, so let's throw a more useful error than 74 | // "undefined is not a function" 75 | throw new Error(methodName + ' on ' + storeName + 76 | ' has been removed but was still called.'); 77 | } 78 | } 79 | var state = this.state; 80 | var args = [state].concat(Array.prototype.slice.call(arguments)); 81 | return spec.getters[methodName].apply(null, args); 82 | }; 83 | } 84 | }); 85 | }; 86 | 87 | ReducerStore.replaceSpec = function replaceSpec(spec) { 88 | spec = spec || {}; 89 | ReducerStore.replaceReducers(spec.reducers); 90 | ReducerStore.replaceGetters(spec.getters); 91 | }; 92 | 93 | ReducerStore.replaceSpec(spec); 94 | 95 | return ReducerStore; 96 | }; 97 | -------------------------------------------------------------------------------- /packages/fluxible-addons-react/connectToStores.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015, Yahoo Inc. 3 | * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. 4 | */ 5 | 'use strict'; 6 | 7 | var React = require('react'); 8 | var hoistNonReactStatics = require('hoist-non-react-statics'); 9 | 10 | function createComponent(Component, stores, getStateFromStores, customContextTypes) { 11 | var componentName = Component.displayName || Component.name; 12 | var componentContextTypes = Object.assign({ 13 | getStore: React.PropTypes.func.isRequired 14 | }, customContextTypes); 15 | var StoreConnector = React.createClass({ 16 | displayName: componentName + 'StoreConnector', 17 | contextTypes: componentContextTypes, 18 | getInitialState: function getInitialState() { 19 | return this.getStateFromStores(); 20 | }, 21 | componentDidMount: function componentDidMount() { 22 | stores.forEach(function storesEach(Store) { 23 | this.context.getStore(Store).addChangeListener(this._onStoreChange); 24 | }, this); 25 | }, 26 | componentWillUnmount: function componentWillUnmount() { 27 | stores.forEach(function storesEach(Store) { 28 | this.context.getStore(Store).removeChangeListener(this._onStoreChange); 29 | }, this); 30 | }, 31 | componentWillReceiveProps: function componentWillReceiveProps(nextProps){ 32 | this.setState(this.getStateFromStores(nextProps)); 33 | }, 34 | getStateFromStores: function (props) { 35 | props = props || this.props; 36 | return getStateFromStores(this.context, props); 37 | }, 38 | _onStoreChange: function onStoreChange() { 39 | if (this.isMounted()) { 40 | this.setState(this.getStateFromStores()); 41 | } 42 | }, 43 | render: function render() { 44 | return React.createElement(Component, Object.assign({}, this.props, this.state, {ref: 'wrappedElement'})); 45 | } 46 | }); 47 | 48 | hoistNonReactStatics(StoreConnector, Component); 49 | 50 | return StoreConnector; 51 | } 52 | 53 | /** 54 | * Registers change listeners and retrieves state from stores using the `getStateFromStores` 55 | * method. Concept provided by Dan Abramov via 56 | * https://medium.com/@dan_abramov/mixins-are-dead-long-live-higher-order-components-94a0d2f9e750 57 | * 58 | * Example: 59 | * connectToStores(Component, [FooStore], { 60 | * FooStore: function (store, props) { 61 | * return { 62 | * foo: store.getFoo() 63 | * } 64 | * } 65 | * }) 66 | * 67 | * Also supports the decorator pattern: 68 | * @connectToStores([FooStore], { 69 | * FooStore: function (store, props) { 70 | * return { 71 | * foo: store.getFoo() 72 | * } 73 | * } 74 | * }) 75 | * class ConnectedComponent extends React.Component { 76 | * render() { 77 | * return
    ; 78 | * } 79 | * } 80 | * 81 | * @method connectToStores 82 | * @param {React.Component} [Component] component to pass state as props to. 83 | * @param {array} stores List of stores to listen for changes 84 | * @param {function} getStateFromStores function that receives all stores and should return 85 | * the full state object. Receives `stores` hash and component `props` as arguments 86 | * @returns {React.Component} or {Function} if using decorator pattern 87 | */ 88 | module.exports = function connectToStores(Component, stores, getStateFromStores) { 89 | 90 | // support decorator pattern 91 | if (arguments.length === 2) { 92 | var _stores = Component; 93 | var _getStateFromStores = stores; 94 | return function connectToStoresDecorator(ComponentToDecorate) { 95 | return createComponent(ComponentToDecorate, _stores, _getStateFromStores); 96 | }; 97 | } 98 | 99 | return createComponent.apply(null, arguments); 100 | }; 101 | -------------------------------------------------------------------------------- /packages/fluxible/docs/api/Plugins.md: -------------------------------------------------------------------------------- 1 | # API: `Plugins` 2 | 3 | Plugins allow you to extend the interface of each context type. 4 | 5 | ## Creating Plugins 6 | 7 | Here, we'll give components access to the `getFoo()` function: 8 | 9 | ```js 10 | import Fluxible from 'fluxible'; 11 | let app = new Fluxible(); 12 | 13 | app.plug({ 14 | // Required unique name property 15 | name: 'TestPlugin', 16 | 17 | /** 18 | * Called after context creation to dynamically create a context plugin 19 | * @method plugContext 20 | * @param {Object} options Options passed into createContext 21 | * @param {Object} context FluxibleContext instance 22 | * @param {Object} app Fluxible instance 23 | */ 24 | plugContext: function (options, context, app) { 25 | // `options` is the same as what is passed into `Fluxible.createContext(options)` 26 | let foo = options.foo; 27 | 28 | // Returns a context plugin 29 | return { 30 | /** 31 | * Method called to allow modification of the component context 32 | * @method plugComponentContext 33 | * @param {Object} componentContext Options passed into createContext 34 | * @param {Object} context FluxibleContext instance 35 | * @param {Object} app Fluxible instance 36 | */ 37 | plugComponentContext: function (componentContext, context, app) { 38 | componentContext.getFoo = function () { 39 | return foo; 40 | }; 41 | }, 42 | //plugActionContext: function (actionContext, context, app) {} 43 | //plugStoreContext: function (storeContext, context, app) {} 44 | 45 | /** 46 | * Allows context plugin settings to be persisted between server and client. Called on server 47 | * to send data down to the client 48 | * @method dehydrate 49 | */ 50 | dehydrate: function () { 51 | return { 52 | foo: foo 53 | }; 54 | }, 55 | 56 | /** 57 | * Called on client to rehydrate the context plugin settings 58 | * @method rehydrate 59 | * @param {Object} state Object to rehydrate state 60 | */ 61 | rehydrate: function (state) { 62 | foo = state.foo; 63 | } 64 | }; 65 | }, 66 | 67 | /** 68 | * Allows dehydration of application plugin settings 69 | * @method dehydrate 70 | */ 71 | dehydrate: function () { return {}; }, 72 | 73 | /** 74 | * Allows rehydration of application plugin settings 75 | * @method rehydrate 76 | * @param {Object} state Object to rehydrate state 77 | */ 78 | rehydrate: function (state) {} 79 | }); 80 | 81 | // set a value for it 82 | let context = app.createContext({ 83 | foo: 'bar' 84 | }); 85 | 86 | // retrieve it from actions, stores or components 87 | context.getActionContext().getFoo(); // returns 'bar' 88 | context.getStoreContext().getFoo(); // returns 'bar' 89 | context.getComponentContext().getFoo(); // returns 'bar' 90 | // or this.context.getFoo() from a React component, read the note below 91 | ``` 92 | 93 | ### componentContext 94 | 95 | To leverage the new context value in componentContext, `provideContext` has an option for [passing custom contextTypes](http://fluxible.io/addons/provideContext.html#plugins-and-custom-component-context) which will ensure that it is made available to all children. 96 | 97 | ``` 98 | // create the React component as usual 99 | class Application extends React.Component { 100 | render() { 101 | ... 102 | } 103 | } 104 | 105 | // pass in the "foo" context type to register it with React context 106 | Application = provideContext(Application, { 107 | foo: React.PropTypes.string 108 | }); 109 | 110 | ``` 111 | 112 | Example plugins: 113 | * [fluxible-plugin-fetchr](https://github.com/yahoo/fluxible-plugin-fetchr) - Polymorphic RESTful services 114 | * [Search for more on npm](https://www.npmjs.com/search?q=fluxible+plugin) 115 | -------------------------------------------------------------------------------- /packages/fluxible/docs/api/Fluxible.md: -------------------------------------------------------------------------------- 1 | # API: `Fluxible` 2 | 3 | Instantiated once for your application, this holds settings and interfaces that are used across all requests. 4 | 5 | ## Usage 6 | 7 | ```js 8 | import Fluxible from 'fluxible'; 9 | const fluxibleApp = new Fluxible({ 10 | component: require('./components/App.jsx') 11 | }); 12 | ``` 13 | 14 | For each request: 15 | 16 | ```js 17 | const context = fluxibleApp.createContext(); 18 | context.executeAction(action, payload, function () { 19 | const element = React.createElement(context.getComponent(), { 20 | context: context.getComponentContext() 21 | }); 22 | const markup = ReactDOM.renderToString(element); 23 | const state = fluxibleApp.dehydrate(context); 24 | 25 | // ... send markup and state to the client ... 26 | }); 27 | ``` 28 | 29 | ## Methods 30 | 31 | ### Constructor 32 | 33 | Creates a new application instance with the following parameters: 34 | 35 | * `options`: An object containing the application settings 36 | * `options.component` (optional): Stores your top level React component for access using `getComponent()` 37 | * `options.componentActionErrorHandler` (optional): App level component action handler. 38 | 39 | #### `options.component` 40 | 41 | This is a convenience method that allows you to access your component via 42 | `context.getComponent()`. This is useful for when you want to eventually 43 | render your application, you can pass the returned component to your render 44 | method. 45 | 46 | #### `options.componentActionErrorHandler` 47 | 48 | `componentActionErrorHandler` is an action that will be called in the event of 49 | an error being triggered from within an action that was called by a component's 50 | `executeAction`. This is important to handle since components are not able to 51 | pass callbacks to `executeAction`. By default, the application will `throw` the 52 | error but you can override this: 53 | 54 | ```js 55 | const fluxibleApp = new Fluxible({ 56 | component: require('./components/App.jsx'), 57 | componentActionErrorHandler: function (context, payload, done) { 58 | var err = payload.err; 59 | // Handle error by setting error state 60 | context.dispatch('APPLICATION_ERROR', err); 61 | done(); 62 | } 63 | }); 64 | ``` 65 | 66 | ### createContext(contextOptions) 67 | 68 | Creates a new [FluxibleContext](FluxibleContext.md) instance passing the `contextOptions` into the constructor. Also iterates over the plugins calling `plugContext` on the plugin if it exists in order to allow dynamic modification of the context. 69 | 70 | ## registerStore(storeClass) 71 | 72 | Registers a [store](Stores.md) to the application making them available to handle actions and be accessible through `getStore` on the [FluxibleContext](FluxibleContext.md). 73 | 74 | ### plug(plugin) 75 | 76 | Allows custom application wide settings to be shared between server and client. Also allows dynamically plugging the [FluxibleContext](FluxibleContext.md) instance each time it is created by implementing a `plugContext` function that receives the context options. 77 | 78 | ### getPlugin(pluginName) 79 | 80 | Provides access to get a plugged plugin by name. 81 | 82 | ### getComponent() 83 | 84 | Provides access to the `options.component` that was passed to the constructor. This is useful if you want to create the application in a file that is shared both server and client but then need to access the top level component in server and client specific files. 85 | 86 | ### dehydrate(context) 87 | 88 | Returns a serializable object containing the state of the Fluxible and passed FluxibleContext instances. This is useful for serializing the state of the application to send it to the client. This will also call any plugins which contain a dehydrate method. 89 | 90 | ### rehydrate(state, callback) 91 | 92 | Takes an object representing the state of the Fluxible and FluxibleContext instances (usually retrieved from dehydrate) to rehydrate them to the same state as they were on the server. This will also call any plugins which contain a rehydrate method. This method is asynchronous to allow for plugins to load necessary assets or data needed for bootstrapping the application. The callback receives the following: 93 | 94 | * `err` - If rehydration had an error 95 | * `context` - A newly created context that is rehydrated to the server state 96 | -------------------------------------------------------------------------------- /packages/fluxible-addons-react/tests/unit/lib/batchedUpdatePlugin.js: -------------------------------------------------------------------------------- 1 | /*globals describe,it,afterEach,beforeEach,document*/ 2 | 'use strict'; 3 | 4 | var expect = require('chai').expect; 5 | var mockery = require('mockery'); 6 | var React; 7 | var ReactDOM; 8 | var ReactTestUtils; 9 | var connectToStores; 10 | var provideContext; 11 | var FooStore = require('../../fixtures/stores/FooStore'); 12 | var BarStore = require('../../fixtures/stores/BarStore'); 13 | var Fluxible = require('fluxible'); 14 | var jsdom = require('jsdom'); 15 | 16 | describe('fluxible-addons-react', function () { 17 | describe('connectToStores', function () { 18 | var appContext; 19 | 20 | beforeEach(function (done) { 21 | jsdom.env('', [], function (err, window) { 22 | if (err) { 23 | done(err); 24 | } 25 | global.window = window; 26 | global.document = window.document; 27 | global.navigator = window.navigator; 28 | 29 | mockery.enable({ 30 | warnOnReplace: false, 31 | warnOnUnregistered: false, 32 | useCleanCache: true 33 | }); 34 | React = require('react'); 35 | ReactDOM = require('react-dom'); 36 | ReactTestUtils = require('react-addons-test-utils'); 37 | connectToStores = require('../../../').connectToStores; 38 | provideContext = require('../../../').provideContext; 39 | 40 | var batchedUpdatePlugin = require('../../../batchedUpdatePlugin'); 41 | var app = new Fluxible({ 42 | stores: [FooStore, BarStore] 43 | }); 44 | app.plug(batchedUpdatePlugin()); 45 | 46 | appContext = app.createContext(); 47 | 48 | done(); 49 | }); 50 | }); 51 | 52 | afterEach(function () { 53 | delete global.window; 54 | delete global.document; 55 | delete global.navigator; 56 | mockery.disable(); 57 | }); 58 | 59 | it('should only call render once when two stores emit changes', function (done) { 60 | var i = 0; 61 | var Component = React.createClass({ 62 | componentDidUpdate: function () { 63 | i++; 64 | }, 65 | render: function () { 66 | return ( 67 |
    68 | {this.props.foo} 69 | {this.props.bar} 70 |
    71 | ); 72 | } 73 | }); 74 | var WrappedComponent = provideContext(connectToStores(Component, [FooStore, BarStore], (context) => ({ 75 | foo: context.getStore(FooStore).getFoo(), 76 | bar: context.getStore(BarStore).getBar() 77 | }))); 78 | 79 | var container = document.createElement('div'); 80 | var component = ReactDOM.render(, container); 82 | var wrappedElement = component.refs.wrappedElement.refs.wrappedElement; 83 | 84 | ReactDOM.unstable_batchedUpdates(function () { 85 | wrappedElement.setState({ 86 | foo: 'far', 87 | bar: 'baz' 88 | }); 89 | wrappedElement.setState({ 90 | foo: 'far', 91 | bar: 'baz' 92 | }); 93 | }); 94 | 95 | expect(i).to.equal(1); 96 | i = 0; 97 | 98 | appContext.executeAction(function (actionContext) { 99 | actionContext.dispatch('DOUBLE_UP'); 100 | }); 101 | (function checkForFinalState() { 102 | var props = wrappedElement.props; 103 | if (props && props.foo === 'barbar' && props.bar === 'bazbaz') { 104 | if (i > 1) { 105 | done(new Error('Update called ' + i + ' times during dispatch')); 106 | return; 107 | } 108 | done(); 109 | } else { 110 | setTimeout(checkForFinalState, 5); 111 | } 112 | })(); 113 | }); 114 | }); 115 | }); 116 | -------------------------------------------------------------------------------- /packages/fluxible/README.md: -------------------------------------------------------------------------------- 1 | # Fluxible 2 | 3 | [![NPM version](https://badge.fury.io/js/fluxible.svg)](http://badge.fury.io/js/fluxible) 4 | 10 | 11 | Pluggable, singleton-free container for isomorphic [Flux](https://github.com/facebook/flux) applications. 12 | 13 | ```bash 14 | $ npm install --save fluxible 15 | ``` 16 | 17 | Join the #fluxible channel of the [Reactiflux](http://reactiflux.com) Discord community. 18 | 19 | [![Gitter chat](https://badges.gitter.im/gitterHQ/gitter.png)](https://gitter.im/yahoo/fluxible) 20 | 21 | ## Docs 22 | 23 | * [Quick Start](https://github.com/yahoo/fluxible/blob/master/packages/fluxible/docs/quick-start.md) 24 | * [API](https://github.com/yahoo/fluxible/blob/master/packages/fluxible/docs/api/README.md) 25 | 26 | ## Features 27 | 28 | * Singleton-free for server rendering 29 | * [Store](https://github.com/yahoo/fluxible/blob/master/packages/fluxible/docs/api/Stores.md) dehydration for client bootstrapping 30 | * Stateless async [actions](https://github.com/yahoo/fluxible/blob/master/packages/fluxible/docs/api/Actions.md) 31 | * Higher order [components](https://github.com/yahoo/fluxible/blob/master/packages/fluxible/docs/api/Components.md) for easy integration 32 | * Enforcement of Flux flow - restricted access to the [Flux interface](https://github.com/yahoo/fluxible/blob/master/packages/fluxible/docs/api/FluxibleContext.md) from within components 33 | * [Pluggable](https://github.com/yahoo/fluxible/blob/master/packages/fluxible/docs/api/Plugins.md) - add your own interfaces to the Flux context 34 | * Updated for React 0.13 35 | 36 | ## Extras 37 | 38 | * [Yeoman generator](https://github.com/yahoo/generator-fluxible) 39 | * [Example Applications](https://github.com/yahoo/flux-examples) 40 | * [Fluxible Routing](https://github.com/yahoo/fluxible-router) 41 | * [Isomorphic Data Services](https://github.com/yahoo/fluxible-plugin-fetchr) 42 | 43 | ## Usage 44 | 45 | ```js 46 | import Fluxible from 'fluxible'; 47 | import {createStore} from 'fluxible/addons'; 48 | import { 49 | connectToStores, 50 | createElementWithContext, 51 | provideContext 52 | } from 'fluxible-addons-react'; 53 | import React from 'react'; 54 | import ReactDOM from 'react-dom/server'; 55 | 56 | // Action 57 | const action = (actionContext, payload) => { 58 | actionContext.dispatch('FOO_ACTION', payload); 59 | }; 60 | 61 | // Store 62 | const FooStore = createStore({ 63 | storeName: 'FooStore', 64 | handlers: { 65 | 'FOO_ACTION': 'fooHandler' 66 | }, 67 | initialize: function () { // Set the initial state 68 | this.foo = null; 69 | }, 70 | fooHandler: function (payload) { 71 | this.foo = payload; 72 | }, 73 | getState: function () { 74 | return { 75 | foo: this.foo 76 | } 77 | } 78 | }); 79 | 80 | // Component 81 | class App extends React.Component { 82 | render() { 83 | return {this.props.foo} 84 | } 85 | } 86 | 87 | App = provideContext(connectToStores(App, [FooStore], (context, props) => { 88 | return context.getStore(FooStore).getState(); 89 | })); 90 | 91 | // App 92 | const app = new Fluxible({ 93 | component: App, 94 | stores: [FooStore] 95 | }); 96 | 97 | // Bootstrap 98 | const context = app.createContext(); 99 | context.executeAction(action, 'bar', (err) => { 100 | console.log(ReactDOM.renderToString(createElementWithContext(context))); 101 | }); 102 | ``` 103 | 104 | ## Browser Compatibility 105 | 106 | Fluxible is written with ES2015 in mind and should be used along with polyfills 107 | for features like [`Promise`][Promise] and [`Object.assign`][objectAssign] 108 | in order to support all browsers. We recommend using [Babel][babel] along with 109 | its [polyfill][polyfill]. 110 | 111 | ## License 112 | 113 | This software is free to use under the Yahoo Inc. BSD license. 114 | See the [LICENSE file][] for license text and copyright information. 115 | 116 | [Promise]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise 117 | [objectAssign]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign 118 | [babel]: https://babeljs.io/ 119 | [polyfill]: https://babeljs.io/docs/usage/polyfill/ 120 | [LICENSE file]: https://github.com/yahoo/fluxible/blob/master/LICENSE.md 121 | -------------------------------------------------------------------------------- /packages/fluxible-addons-react/tests/unit/lib/provideContext.js: -------------------------------------------------------------------------------- 1 | /*globals describe,it*/ 2 | 'use strict'; 3 | 4 | var expect = require('chai').expect; 5 | var React = require('react'); 6 | var ReactDOM = require('react-dom/server'); 7 | var provideContext = require('../../..').provideContext; 8 | 9 | describe('fluxible-addons-react', function () { 10 | describe('provideContext', function () { 11 | it('should use the childs name', function () { 12 | var Component = React.createClass({ 13 | render: function () { 14 | return null; 15 | } 16 | }); 17 | 18 | var WrappedComponent = provideContext(Component); 19 | expect(WrappedComponent.displayName).to.equal('ComponentContextProvider'); 20 | }); 21 | 22 | it('should use the childs displayName', function () { 23 | var Component = React.createClass({ 24 | displayName: 'TestComponent', 25 | render: function () { 26 | return null; 27 | } 28 | }); 29 | 30 | var WrappedComponent = provideContext(Component); 31 | expect(WrappedComponent.displayName).to.equal('TestComponentContextProvider'); 32 | }); 33 | 34 | it('should provide the context with custom types to children', function () { 35 | var context = { 36 | foo: 'bar', 37 | executeAction: function () { 38 | }, 39 | getStore: function () { 40 | } 41 | }; 42 | var Component = React.createClass({ 43 | contextTypes: { 44 | foo: React.PropTypes.string.isRequired, 45 | executeAction: React.PropTypes.func.isRequired, 46 | getStore: React.PropTypes.func.isRequired 47 | }, 48 | render: function () { 49 | expect(this.context.foo).to.equal(context.foo); 50 | expect(this.context.executeAction).to.equal(context.executeAction); 51 | expect(this.context.getStore).to.equal(context.getStore); 52 | return null; 53 | } 54 | }); 55 | var WrappedComponent = provideContext(Component, { 56 | foo: React.PropTypes.string 57 | }); 58 | 59 | ReactDOM.renderToString(); 60 | }); 61 | 62 | it('should hoist non-react statics to higher order component', function () { 63 | var context = { 64 | foo: 'bar', 65 | executeAction: function () { 66 | }, 67 | getStore: function () { 68 | } 69 | }; 70 | var Component = React.createClass({ 71 | displayName: 'Component', 72 | statics: { 73 | initAction: function () { 74 | } 75 | }, 76 | render: function () { 77 | expect(this.context.foo).to.equal(context.foo); 78 | expect(this.context.executeAction).to.equal(context.executeAction); 79 | expect(this.context.getStore).to.equal(context.getStore); 80 | return null; 81 | } 82 | }); 83 | var WrappedComponent = provideContext(Component, { 84 | foo: React.PropTypes.string 85 | }); 86 | 87 | expect(WrappedComponent.initAction).to.be.a('function'); 88 | expect(WrappedComponent.displayName).to.not.equal(Component.displayName); 89 | }); 90 | 91 | it('should provide the context with custom types to children using the decorator pattern', function () { 92 | var context = { 93 | foo: 'bar', 94 | executeAction: function () { 95 | }, 96 | getStore: function () { 97 | } 98 | }; 99 | @provideContext({ 100 | foo: React.PropTypes.string 101 | }) class WrappedComponent extends React.Component { 102 | static contextTypes = { 103 | foo: React.PropTypes.string.isRequired, 104 | executeAction: React.PropTypes.func.isRequired, 105 | getStore: React.PropTypes.func.isRequired 106 | }; 107 | 108 | render() { 109 | expect(this.context.foo).to.equal(context.foo); 110 | expect(this.context.executeAction).to.equal(context.executeAction); 111 | expect(this.context.getStore).to.equal(context.getStore); 112 | return null; 113 | } 114 | } 115 | 116 | ReactDOM.renderToString(); 117 | }); 118 | }); 119 | }); 120 | -------------------------------------------------------------------------------- /packages/fluxible/docs/api/FluxibleContext.md: -------------------------------------------------------------------------------- 1 | # API: `FluxibleContext` 2 | 3 | Instantiated once per request/session by calling `Fluxible.createContext(options)`, this container provides isolation of stores, dispatches, and other data so that it is not shared between requests on the server side. 4 | 5 | ## SubContexts 6 | 7 | Within a `FluxibleContext`, each component of your application receives a subset of the methods within the `FluxibleContext`. This prevents the components from breaking out of the Flux flow. These subcontexts are modifiable by [plugins](Plugins.md). Each subcontext has a corresponding getter on the `FluxibleContext`, for instance `getComponentContext()`, although for the most part, they will be provided to you within your components without needing to call the getter. 8 | 9 | * [Action Context](Actions.md#action-context) - Passed as first parameter to all actions. Has access to most methods within Fluxible. 10 | * [Component Context](Components.md#component-context) - Passed as a prop to top level React component and then propagated to child components that require access to it. 11 | * [Store Context](Stores.md#store-context) - Passed as first parameter to all store constructors. By default has no methods or properties. 12 | 13 | | | FluxibleContext | Action Context | Component Context | Store Context | 14 | |--------------- |----------------- |---------------- |------------------- |--------------- | 15 | | dispatch | | ✓ | | | 16 | | executeAction | ✓ | ✓ | no callback | | 17 | | getStore | ✓ | ✓ | ✓ |   | 18 | 19 | ## Methods 20 | 21 | ### Constructor 22 | 23 | Creates a new context instance with the following parameters: 24 | 25 | * `options`: An object containing the context settings 26 | * `options.app`: Provides access to the application level functions and settings 27 | 28 | ### executeAction(action, payload, [done]) 29 | 30 | This is the entry point into an application's execution. The initial action is what begins the flux flow: action dispatches events to stores and stores update their data structures. On the server, we wait for the initial action to finish and then we're ready to render using React. On the client, the components are already rendered and are waiting for store change events. 31 | 32 | Parameters: 33 | 34 | * `action`: A function that takes three parameters: `actionContext`, `payload`, `done` 35 | * `payload`: the action payload 36 | * `done`: optional callback to call when the action has been completed. Receives error as the first parameter and result as the second. 37 | 38 | Returns: 39 | 40 | * `executeActionPromise`: promise that is resolved once the action is completed, or rejected if it has an error 41 | 42 | **Callback** 43 | 44 | ```js 45 | let action = function (actionContext, payload, done) { 46 | // do stuff 47 | done(); 48 | }; 49 | context.executeAction(action, {}, function (err) { 50 | // action has completed 51 | }); 52 | ``` 53 | 54 | **Promise** 55 | 56 | ```js 57 | var action = function (actionContext, payload, done) { 58 | // do stuff 59 | done(); 60 | }; 61 | context.executeAction(action, {}) 62 | .then(function (result) { 63 | // action has completed 64 | }) 65 | .catch(function (err) { 66 | // action had an error 67 | }); 68 | ``` 69 | 70 | ### plug(plugin) 71 | 72 | Allows custom context settings to be shared between server and client. Also allows dynamically plugging the ActionContext, ComponentContext, and StoreContext to provide additional methods. 73 | 74 | ### getActionContext() 75 | 76 | Returns the [Action Context](Actions.md#action-context) which provides access to only the functions that should be called from actions. By default: `dispatch`, `executeAction`, and `getStore`. 77 | 78 | This action context object is used every time an `executeAction` method is called and is passed as the first parameter to the action. 79 | 80 | ### getComponentContext() 81 | 82 | Returns the [Component Context](Components.md#Component Context) which provides access to only the functions that should be called from components. By default: `executeAction`, `getStore`. `executeAction` does not allow passing a callback from components so that it enforces actions to be send and forget. 83 | 84 | *Note: You may provide an app level `componentActionErrorHandler` function when instantiating Fluxible. This allows you to catch errors (at a high level) spawning from components firing actions.* 85 | 86 | This context interface should be passed in to your top level React component and then sent down to children as needed. These components will now have access to listen to store instances, execute actions, and access any methods added to the component context by plugins. 87 | 88 | ### getStoreContext() 89 | 90 | Returns the [Store Context](Stores.md#Store Context) which provides access to only the functions that should be called from stores. By default, this is empty, but it is modifiable by plugins. 91 | 92 | ### dehydrate() 93 | 94 | Returns a serializable object containing the state of the `FluxibleContext` and its `Dispatchr` instance. This is useful for serializing the state of the current context to send it to the client. This will also call any plugins whose `plugContext` method returns an object containing a dehydrate method. 95 | 96 | ### rehydrate(state) 97 | 98 | Takes an object representing the state of the `FluxibleContext` and `Dispatchr` instances (usually retrieved from dehydrate) to rehydrate them to the same state as they were on the server. This will also call any plugins whose `plugContext` method returns an object containing a rehydrate method. 99 | -------------------------------------------------------------------------------- /packages/fluxible/docs/api/Stores.md: -------------------------------------------------------------------------------- 1 | # API: `Stores` 2 | 3 | Flux stores are where you keep your application's state and handle business 4 | logic that reacts to data events. Stores in Fluxible are just classes that 5 | adhere to a simple interface. Because we want stores to be able to be completely 6 | decoupled from Fluxible, we do not provide any store implementation in our 7 | default exports, however you can use the [helper utilities](#helper-utilities) 8 | for creating your stores. 9 | 10 | ```js 11 | import {EventEmitter} from 'events'; 12 | 13 | class ApplicationStore extends EventEmitter { 14 | constructor (dispatcher) { 15 | super(dispatcher); 16 | this.dispatcher = dispatcher; // Provides access to waitFor and getStore methods 17 | this.currentPageName = null; 18 | } 19 | 20 | handleReceivePage (payload) { 21 | this.currentPageName = payload.pageName; 22 | this.emit('change'); 23 | } 24 | 25 | getCurrentPageName () { 26 | return this.currentPageName; 27 | } 28 | 29 | // For sending state to the client 30 | dehydrate () { 31 | return this.getState(); 32 | } 33 | 34 | // For rehydrating server state 35 | rehydrate (state) { 36 | this.currentPageName = state.currentPageName; 37 | } 38 | } 39 | 40 | ApplicationStore.storeName = 'ApplicationStore'; 41 | ApplicationStore.handlers = { 42 | 'RECEIVE_PAGE': 'handleReceivePage' 43 | }; 44 | 45 | export default ApplicationStore; 46 | ``` 47 | 48 | ## Helper Utilities 49 | 50 | Fluxible provides a couple of helpers for building stores with some default 51 | behavior and convenience methods. 52 | 53 | * [BaseStore](addons/BaseStore.md) - Store class that can be extended 54 | * [createStore](addons/createStore.md) - function for creating a class that 55 | extends `BaseStore` 56 | 57 | ## Interface 58 | 59 | ### Constructor 60 | 61 | The store should have a constructor function that will be used to instantiate 62 | your store using `new Store(dispatcher)` where the parameters are as 63 | follows: 64 | 65 | * `dispatcher`: An object providing access to the following methods: 66 | * `dispatcher.getContext()`: Retrieve the [store context](#store-context) 67 | * `dispatcher.getStore(storeClass)` 68 | * `dispatcher.waitFor(storeClass[], callback)` 69 | 70 | The constructor is also where the initial state of the store should be set. 71 | 72 | ```js 73 | class ExampleStore { 74 | constructor (dispatcher) { 75 | this.dispatcher = dispatcher; 76 | if (this.initialize) { 77 | this.initialize(); 78 | } 79 | } 80 | } 81 | ``` 82 | 83 | It is also recommended to extend an event emitter so that your store can emit 84 | `change` events to the components. 85 | 86 | ```js 87 | class ExampleStore extends EventEmitter { 88 | // ... 89 | } 90 | ``` 91 | 92 | ### Static Properties 93 | 94 | #### storeName 95 | 96 | The store should define a static property that gives the name of the store. This 97 | is used internally and for debugging purposes. 98 | 99 | ```js 100 | ExampleStore.storeName = 'ExampleStore'; 101 | ``` 102 | 103 | #### handlers 104 | 105 | The store should define a static property that maps action names to handler 106 | functions or method names. These functions will be called in the event that an 107 | action has been dispatched by the Dispatchr instance. 108 | 109 | ```js 110 | ExampleStore.handlers = { 111 | 'NAVIGATE': 'handleNavigate', 112 | 'default': 'defaultHandler' // Called for any action that has not been otherwise handled 113 | }; 114 | ``` 115 | 116 | The handler function will be passed two parameters: 117 | 118 | * `payload`: An object containing action information. 119 | * `actionName`: The name of the action. This is primarily useful when using 120 | the `default` handler 121 | 122 | ```js 123 | class ExampleStore { 124 | handleNavigate (payload, actionName) { 125 | this.navigating = true; 126 | this.emit('change'); // Component may be listening for changes to state 127 | } 128 | } 129 | ``` 130 | 131 | If you prefer to define private methods for handling actions, you can use a 132 | static function instead of a method name. This function will be bound to the 133 | store instance when it is called: 134 | 135 | ```js 136 | ExampleStore.handlers = { 137 | 'NAVIGATE': function handleNavigate(payload, actionName) { 138 | // bound to store instance 139 | this.navigating = true; 140 | this.emit('change'); 141 | } 142 | }; 143 | ``` 144 | 145 | ### Instance Methods 146 | 147 | #### dehydrate() 148 | 149 | The store should define this function to dehydrate the store if it will be 150 | shared between server and client. It should return a serializable data object 151 | that will be passed to the client. 152 | 153 | ```js 154 | class ExampleStore { 155 | dehydrate () { 156 | return { 157 | navigating: this.navigating 158 | }; 159 | } 160 | } 161 | ``` 162 | 163 | #### rehydrate(state) 164 | 165 | The store should define this function to rehydrate the store if it will be 166 | shared between server and client. It should restore the store to the original 167 | state using the passed `state`. 168 | 169 | ```js 170 | class ExampleStore { 171 | rehydrate (state) { 172 | this.navigating = state.navigating; 173 | } 174 | } 175 | ``` 176 | 177 | #### shouldDehydrate() 178 | 179 | The store can optionally define this function to control whether the store state 180 | should be dehydrated by the dispatcher. This method should return a boolean. If 181 | this function is undefined, the store will always be dehydrated (just as if true 182 | was returned from method). 183 | 184 | ```js 185 | class ExampleStore { 186 | shouldDehydrate () { 187 | return true; 188 | } 189 | } 190 | ``` 191 | 192 | ## Store Context 193 | 194 | The store context by default contains no methods, but can be modified by 195 | plugins. 196 | -------------------------------------------------------------------------------- /packages/fluxible-reducer-store/docs/api/createReducerStore.md: -------------------------------------------------------------------------------- 1 | # createReducerStore 2 | 3 | ```js 4 | import {createReducerStore} from 'fluxible-reducer-store'; 5 | ``` 6 | 7 | A helper method for creating reducer stores for Fluxible. 8 | 9 | ## Usage 10 | 11 | ```js 12 | const CountStore = createReducerStore(spec); 13 | ``` 14 | 15 | The spec can contain the following keys: 16 | 17 | ### `storeName` 18 | 19 | As with all stores in Fluxible, you will need an identifier for your store. 20 | 21 | ### `reducers` or `reducer` 22 | 23 | This should be a map of `eventName`s that are dispatched through the dispatcher 24 | to the reducer that should handle that event. Reducers should be stateless and 25 | immutable. 26 | 27 | ```js 28 | { 29 | ...spec, 30 | reducers: { 31 | INCREMENT_COUNTER: (state, payload) => { 32 | return { 33 | ...state, 34 | count: state.count + 1 35 | }; 36 | } 37 | } 38 | } 39 | ``` 40 | 41 | If you prefer to have a single reducer function for all events, you can pass 42 | a single `reducer`: 43 | 44 | ```js 45 | { 46 | ...spec, 47 | reducer: (state, payload, eventName) => { 48 | if ('INCREMENT_COUNTER' === eventName) { 49 | return { 50 | ...state, 51 | count: state.count + 1 52 | }; 53 | } 54 | return state; 55 | } 56 | } 57 | ``` 58 | 59 | ### `initialState` 60 | 61 | To set up the initial state of your store you can pass an object that will 62 | set the state at instantiation time: 63 | 64 | ```js 65 | { 66 | ...spec, 67 | initialState: { 68 | count: 0 69 | } 70 | } 71 | ``` 72 | 73 | ### `getters` 74 | 75 | You can optionally pass a set of getter functions on your store. Getters are 76 | implemented as reducers themselves: they take in state and return the "view" 77 | of that state. 78 | 79 | ```js 80 | { 81 | ...spec, 82 | getters: { 83 | getCount: (state) => { 84 | return state.count; 85 | } 86 | } 87 | } 88 | ``` 89 | 90 | You can still call them like you would a getter on a store though: 91 | 92 | ```js 93 | context.getStore(CountStore).getCount(); 94 | ``` 95 | 96 | This is a convenient way of sharing "views" of your data between several 97 | components. 98 | 99 | ## Full Example 100 | 101 | ```js 102 | const MessageStore = createReducerStore({ 103 | storeName: 'MessageStore', 104 | initialState: { 105 | messages: {}, 106 | sortedByDate: [] 107 | }, 108 | reducers: { 109 | RECEIVE_MESSAGES: (state, messages) => { 110 | var oldMessages = state.messages; 111 | var newMessages = {...oldMessages}; 112 | messages.forEach(function (message) { 113 | newMessages[message.id] = { 114 | ...message, 115 | isRead: false 116 | }; 117 | }); 118 | var sortedByDate = (newMessages && Object.keys(newMessages)) || []; 119 | sortedByDate.sort(function (a, b) { 120 | if (newMessages[a].date < newMessages[b].date) { 121 | return -1; 122 | } else if (newMessages[a].date > newMessages[b].date) { 123 | return 1; 124 | } 125 | return 0; 126 | }); 127 | 128 | return { 129 | messages: newMessages, 130 | sortedByDate 131 | }; 132 | }, 133 | OPEN_THREAD: (state, payload) => { 134 | // Mark all read 135 | var oldMessages = state.messages; 136 | var newMessages = { 137 | ...oldMessages 138 | }; 139 | Object.keys(state.messages).forEach((key) => { 140 | var message = state.messages[key]; 141 | if (message.threadID === payload.threadID) { 142 | newMessages[key] = { 143 | ...message, 144 | text: message.text + 'foo', 145 | isRead: true 146 | }; 147 | } 148 | }); 149 | return { 150 | ...state, 151 | messages: newMessages 152 | }; 153 | } 154 | }, 155 | getters: { 156 | getAll: function getAll(state) { 157 | return state.messages; 158 | }, 159 | get: function get(state, id) { 160 | return state.messages[id]; 161 | }, 162 | getAllForThread: function getAllForThread(state, threadID) { 163 | var threadMessages = []; 164 | state.sortedByDate.forEach(function (key) { 165 | var message = state.messages[key]; 166 | if (message.threadID === threadID) { 167 | threadMessages.push(message); 168 | } 169 | }); 170 | return threadMessages; 171 | } 172 | } 173 | }); 174 | 175 | export default MessageStore; 176 | ``` 177 | 178 | Now the store can be registered as a normal store. 179 | 180 | ## Hot Reloading 181 | 182 | If you are using webpack's hot module replacement, it is easy to make your 183 | reducer stores hot reloadable. We recommend separating the spec and the call 184 | to `createReducerStore`. For example you could have only the spec in your 185 | store file and call `createReducerStore` in your `app.js`. 186 | 187 | Once you have separated the spec and the store construction, you need to add 188 | the `module.hot.accept` call, require the new spec, and call `Store.replaceSpec` 189 | to ensure that the reducer and getter definitions are correctly replaced. 190 | 191 | _Note: any file that requires your store spec 192 | (e.g. `./stores/MessageStore') will have to implement `module.hot.accept` 193 | including components or actions. You can either implement it in all of your 194 | files or remove the `require`s and call `getStore` with the `storeName` string 195 | instead of the spec/constructor: `context.getStore('MessageStore')`._ 196 | 197 | ### Example 198 | 199 | ```js 200 | // ./stores/MessageStore.js 201 | export default { 202 | storeName: 'MessageStore', 203 | reducers: { ...}, 204 | initialState: {} 205 | }; 206 | ``` 207 | 208 | ```js 209 | // ./app.js 210 | 211 | const app = new Fluxible(); 212 | const MessageStore = createReducerStore(require('./stores/MessageStore')); 213 | 214 | app.registerStore(MessageStore); 215 | 216 | if (module.hot) { 217 | module.hot.accept('./stores/MessageStore', function () { 218 | var NewMessageStore = require('./stores/MessageStore'); 219 | MessageStore.replaceSpec(NewMessageStore); 220 | }); 221 | } 222 | ``` 223 | -------------------------------------------------------------------------------- /packages/fluxible/docs/api/Actions.md: -------------------------------------------------------------------------------- 1 | # API: `Actions` 2 | 3 | Actions (called "action creators" in Flux) in Fluxible are stateless functions that receive three parameters: 4 | 5 | * [`actionContext`](#action-context) - Provides access to Flux methods 6 | * `payload` - The action payload 7 | * `done` - A function to be called when the action has been completed 8 | 9 | If the action does not return a promise and takes three parameters, executeAction will wait for the done callback: 10 | 11 | ```js 12 | export default function myAction(actionContext, payload, done) { 13 | setTimeout(function () { // simulate async 14 | actionContext.dispatch('MY_ACTION', payload); 15 | done(); 16 | }, 10); 17 | } 18 | ``` 19 | 20 | If the action returns a promise, executeAction will wait for it to be resolved or rejected: 21 | 22 | ```js 23 | export default function myPromiseAction(actionContext, payload) { 24 | return getServerData(payload).then(function (data) { 25 | actionContext.dispatch('RECEIVED_SERVER_DATA', data); 26 | }); 27 | }; 28 | ``` 29 | 30 | If the action takes less than three parameters, executeAction will resolve the promise with the return value, if any: 31 | 32 | ```js 33 | export default function mySyncAction(actionContext, payload) { 34 | return actionContext.dispatch('MY_ACTION', payload); 35 | } 36 | ``` 37 | 38 | Actions are generally called via [`FluxibleContext.executeAction(myAction, payload, [done])`](FluxibleContext.md#executeactionaction-payload-callback) but actions can also be fired by other actions: 39 | 40 | **Callback** 41 | 42 | ```js 43 | export default function myParentAction(actionContext, payload, done) { 44 | actionContext.executeAction(myAction, payload, done); 45 | } 46 | ``` 47 | 48 | **Promise** 49 | 50 | ```js 51 | export default function myParentAction(actionContext, payload) { 52 | return actionContext.executeAction(myAction, payload) 53 | .then(function (result) { 54 | // do something 55 | }); 56 | } 57 | ``` 58 | 59 | 60 | or from a [component](Components.md): 61 | 62 | ```js 63 | import myAction from './myAction'; 64 | class MyComponent extends React.Component { 65 | constructor (props) { 66 | super(props); 67 | this.onClick = this.onClick.bind(this); 68 | } 69 | onClick (e) { 70 | this.context.executeAction(myAction, {}); 71 | } 72 | render () { 73 | return
    72 | ); 73 | } 74 | }); 75 | var WrappedComponent = provideContext(connectToStores(Component, [FooStore, BarStore], (context) => ({ 76 | foo: context.getStore(FooStore).getFoo(), 77 | bar: context.getStore(BarStore).getBar() 78 | }))); 79 | 80 | var container = document.createElement('div'); 81 | var component = ReactDOM.render(, container); 83 | var domNode = ReactDOM.findDOMNode(component); 84 | expect(domNode.querySelector('#foo').textContent).to.equal('bar'); 85 | expect(domNode.querySelector('#bar').textContent).to.equal('baz'); 86 | 87 | ReactTestUtils.Simulate.click(domNode.querySelector('#button')); 88 | 89 | expect(domNode.querySelector('#foo').textContent).to.equal('barbar'); 90 | expect(domNode.querySelector('#bar').textContent).to.equal('bazbaz'); 91 | 92 | expect(appContext.getStore(BarStore).listeners('change').length).to.equal(1); 93 | expect(appContext.getStore(FooStore).listeners('change').length).to.equal(1); 94 | 95 | ReactDOM.unmountComponentAtNode(container); 96 | 97 | expect(appContext.getStore(BarStore).listeners('change').length).to.equal(0); 98 | expect(appContext.getStore(FooStore).listeners('change').length).to.equal(0); 99 | done(); 100 | }); 101 | 102 | it('should get the state from the stores using decorator pattern', function (done) { 103 | @connectToStores([FooStore, BarStore], (context) => { 104 | return { 105 | foo: context.getStore(FooStore).getFoo(), 106 | bar: context.getStore(BarStore).getBar() 107 | }; 108 | }) class Component extends React.Component { 109 | static contextTypes = { 110 | executeAction: React.PropTypes.func.isRequired 111 | }; 112 | 113 | onClick() { 114 | this.context.executeAction(function (actionContext) { 115 | actionContext.dispatch('DOUBLE_UP'); 116 | }); 117 | } 118 | 119 | render() { 120 | return ( 121 |
    122 | {this.props.foo} 123 | {this.props.bar} 124 |
    127 | ); 128 | } 129 | } 130 | 131 | var WrappedComponent = provideContext(Component); 132 | 133 | var container = document.createElement('div'); 134 | var component = ReactDOM.render(, container); 136 | var domNode = ReactDOM.findDOMNode(component); 137 | expect(domNode.querySelector('#foo').textContent).to.equal('bar'); 138 | expect(domNode.querySelector('#bar').textContent).to.equal('baz'); 139 | 140 | ReactTestUtils.Simulate.click(domNode.querySelector('#button')); 141 | 142 | expect(domNode.querySelector('#foo').textContent).to.equal('barbar'); 143 | expect(domNode.querySelector('#bar').textContent).to.equal('bazbaz'); 144 | 145 | expect(appContext.getStore(BarStore).listeners('change').length).to.equal(1); 146 | expect(appContext.getStore(FooStore).listeners('change').length).to.equal(1); 147 | 148 | ReactDOM.unmountComponentAtNode(container); 149 | 150 | expect(appContext.getStore(BarStore).listeners('change').length).to.equal(0); 151 | expect(appContext.getStore(FooStore).listeners('change').length).to.equal(0); 152 | done(); 153 | }); 154 | 155 | it('should hoist non-react statics to higher order component', function () { 156 | var Component = React.createClass({ 157 | displayName: 'Component', 158 | statics: { 159 | initAction: function () { 160 | } 161 | }, 162 | render: function () { 163 | return ( 164 |

    Hello world.

    165 | ); 166 | } 167 | }); 168 | var WrapperComponent = provideContext(connectToStores(Component, [FooStore, BarStore], { 169 | displayName: 'WrapperComponent', 170 | FooStore: function (store, props) { 171 | return { 172 | foo: store.getFoo() 173 | }; 174 | }, 175 | BarStore: function (store, props) { 176 | return { 177 | bar: store.getBar() 178 | }; 179 | } 180 | })); 181 | 182 | expect(WrapperComponent.initAction).to.be.a('function'); 183 | expect(WrapperComponent.displayName).to.not.equal(Component.displayName); 184 | }); 185 | }); 186 | }); 187 | -------------------------------------------------------------------------------- /packages/fluxible/lib/Fluxible.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2014, Yahoo! Inc. 3 | * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. 4 | */ 5 | 'use strict'; 6 | 7 | var debug = require('debug')('Fluxible'); 8 | var isPromise = require('is-promise'); 9 | var FluxibleContext = require('./FluxibleContext'); 10 | var dispatchr = require('dispatchr'); 11 | var __DEV__ = process.env.NODE_ENV !== 'production'; 12 | 13 | /* 14 | * The default component action handler 15 | * @method defaultComponentActionHandler 16 | */ 17 | function defaultComponentActionHandler(context, payload, done) { 18 | if (payload.err) { 19 | debug('Action returned error', payload.err); 20 | throw payload.err; 21 | } 22 | done(); 23 | } 24 | 25 | /** 26 | * Provides a structured way of registering an application's configuration and 27 | * resources. 28 | * @class Fluxible 29 | * @param {Object} [options] 30 | * @param {Object} [options.component] Stores your top level React component for access using `getComponent()` 31 | * @param {Function} [options.componentActionErrorHandler] App level component action handler 32 | * @constructor 33 | * 34 | * @example 35 | * var app = new Fluxible({ 36 | * component: require('./components/App.jsx') 37 | * }); 38 | */ 39 | function Fluxible(options) { 40 | debug('Fluxible instance instantiated', options); 41 | options = options || {}; 42 | 43 | // Options 44 | this._component = options.component; 45 | this._componentActionErrorHandler = 46 | options.componentActionErrorHandler || defaultComponentActionHandler; 47 | this._plugins = []; 48 | 49 | // Initialize dependencies 50 | this._dispatcher = dispatchr.createDispatcher(options); 51 | } 52 | 53 | /** 54 | * Creates an isolated context for a request/session 55 | * @method createContext 56 | * @param {Object} [options] 57 | * @returns {FluxibleContext} 58 | */ 59 | Fluxible.prototype.createContext = function createContext(options) { 60 | var self = this; 61 | options = options || {}; 62 | var context = new FluxibleContext(self); 63 | 64 | // Plug context with app plugins that implement plugContext method 65 | this._plugins.forEach(function eachPlugin(plugin) { 66 | if (plugin.plugContext) { 67 | var contextPlugin = plugin.plugContext(options, context, self) || {}; 68 | contextPlugin.name = contextPlugin.name || plugin.name; 69 | context.plug(contextPlugin); 70 | } 71 | }); 72 | 73 | return context; 74 | }; 75 | 76 | /** 77 | * Creates a new dispatcher instance using the application's dispatchr class. Used by 78 | * FluxibleContext to create new dispatcher instance 79 | * @method createDispatcherInstance 80 | * @param {Object} contextOptions The context options to be provided to each store instance 81 | * @returns {Dispatcher} 82 | */ 83 | Fluxible.prototype.createDispatcherInstance = function createDispatcherInstance(contextOptions) { 84 | return this._dispatcher.createContext(contextOptions); 85 | }; 86 | 87 | /** 88 | * Provides plugin mechanism for adding application level settings that are persisted 89 | * between server/client and also modification of the FluxibleContext 90 | * @method plug 91 | * @param {Object} plugin 92 | * @param {String} plugin.name Name of the plugin 93 | * @param {Function} plugin.plugContext Method called after context is created to allow 94 | * dynamically plugging the context 95 | * @param {Object} [plugin.dehydrate] Method called to serialize the plugin settings to be persisted 96 | * to the client 97 | * @param {Object} [plugin.rehydrate] Method called to rehydrate the plugin settings from the server 98 | */ 99 | Fluxible.prototype.plug = function (plugin) { 100 | if (__DEV__) { 101 | if (!plugin.name) { 102 | throw new Error('Application plugin must have a name'); 103 | } 104 | } 105 | this._plugins.push(plugin); 106 | }; 107 | 108 | /** 109 | * Provides access to a plugin instance by name 110 | * @method getPlugin 111 | * @param {String} pluginName The plugin name 112 | * @returns {Object} 113 | */ 114 | Fluxible.prototype.getPlugin = function (pluginName) { 115 | var plugin = null; 116 | this._plugins.forEach(function (p) { 117 | if (pluginName === p.name) { 118 | plugin = p; 119 | } 120 | }); 121 | return plugin; 122 | }; 123 | 124 | /** 125 | * Getter for the top level react component for the application 126 | * @method getComponent 127 | * @returns {ReactComponent} 128 | */ 129 | Fluxible.prototype.getComponent = function getComponent() { 130 | return this._component; 131 | }; 132 | 133 | /** 134 | * Registers a store to the dispatcher so it can listen for actions 135 | * @method registerStore 136 | */ 137 | Fluxible.prototype.registerStore = function registerStore() { 138 | debug(arguments[0].storeName + ' store registered'); 139 | this._dispatcher.registerStore.apply(this._dispatcher, arguments); 140 | }; 141 | 142 | /** 143 | * Creates a serializable state of the application and a given context for sending to the client 144 | * @method dehydrate 145 | * @param {FluxibleContext} context 146 | * @returns {Object} Dehydrated state object 147 | */ 148 | Fluxible.prototype.dehydrate = function dehydrate(context) { 149 | debug('dehydrate', context); 150 | var self = this; 151 | var state = { 152 | context: context.dehydrate(), 153 | plugins: {} 154 | }; 155 | 156 | this._plugins.forEach(function (plugin) { 157 | if ('function' === typeof plugin.dehydrate) { 158 | // Use a namespace for storing plugin state and provide access to the application 159 | state.plugins[plugin.name] = plugin.dehydrate(self); 160 | } 161 | }); 162 | 163 | return state; 164 | }; 165 | 166 | /** 167 | * Rehydrates the application and creates a new context with the state from the server 168 | * @method rehydrate 169 | * @param {Object} obj Raw object of dehydrated state 170 | * @param {Object} obj.plugins Dehydrated app plugin state 171 | * @param {Object} obj.context Dehydrated context state 172 | * @param {Function} callback 173 | * @async Rehydration may require more asset loading or async IO calls 174 | */ 175 | Fluxible.prototype.rehydrate = function rehydrate(obj, callback) { 176 | debug('rehydrate', obj); 177 | var self = this; 178 | if (__DEV__) { 179 | if (typeof obj !== 'object') { 180 | throw new Error('`rehydrate` called with a non-object. Ensure ' + 181 | 'that the parameter passed to rehydrate is a state object ' + 182 | 'produced by a dehydrate call.'); 183 | } 184 | } 185 | obj.plugins = obj.plugins || {}; 186 | var pluginTasks = self._plugins.filter(function (plugin) { 187 | return 'function' === typeof plugin.rehydrate 188 | && obj.plugins[plugin.name]; 189 | }).map(function (plugin) { 190 | return new Promise(function (resolve, reject) { 191 | var result = plugin.rehydrate(obj.plugins[plugin.name], function (err) { 192 | if (err) { 193 | reject(err); 194 | } else { 195 | resolve(); 196 | } 197 | }); 198 | if (isPromise(result)) { 199 | result.then(resolve, reject); 200 | } else if (plugin.rehydrate.length < 2) { 201 | resolve(); 202 | } 203 | }); 204 | }); 205 | 206 | var context = self.createContext(); 207 | var rehydratePromise = Promise.all(pluginTasks).then(function rehydratePluginTasks() { 208 | return context.rehydrate(obj.context || {}); 209 | }); 210 | 211 | if (callback) { 212 | rehydratePromise 213 | .then(function (contextValue) { 214 | // Ensures that errors in callback are not swallowed by promise 215 | setImmediate(callback, null, contextValue); 216 | }, function (err) { 217 | // Ensures that errors in callback are not swallowed by promise 218 | setImmediate(callback, err); 219 | }); 220 | } 221 | 222 | return rehydratePromise; 223 | }; 224 | 225 | module.exports = Fluxible; 226 | -------------------------------------------------------------------------------- /packages/fluxible/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## 1.0.0 4 | 5 | ### API Changes 6 | 7 | * [#257] All previously deprecated React integration APIs have been removed 8 | * [#259] `componentActionHandler` renamed to `componentActionErrorHandler` 9 | * [#258] Removed dependency on `Promise` and `Object.assign` library, must be polyfilled 10 | 11 | ## 0.5.5 12 | 13 | ### Bug Fixes 14 | 15 | * [#276] Lock down fluxible-addons-react dependency for React 0.13 16 | 17 | ## 0.5.4 18 | 19 | ### Bug Fixes 20 | 21 | * [#269] Fixed binding of executeAction to actionContext 22 | 23 | ## 0.5.3 24 | 25 | ### Features 26 | 27 | * [#263] Action tracing: added `actionContext.id` and `actionContext.stack` 28 | 29 | ## 0.5.2 30 | 31 | ### Bug Fixes 32 | 33 | * [#249] Updated `es6-promise`, `chai`, and `eslint` dependencies 34 | * [#248] Updated `object-assign` dependency 35 | 36 | ## 0.5.1 37 | 38 | ### Bug Fixes 39 | 40 | * [#217] Fix warn message `createElement` to `createElementWithContext` 41 | 42 | ## 0.5.0 43 | 44 | ### Features 45 | 46 | * [#147] `connectToStores` and `provideContext` can now be used as decorators 47 | 48 | ### Deprecations 49 | 50 | * [#202] Addons were moved to a separate package ([fluxible-addons-react](https://github.com/yahoo/fluxible-addons-react)) to support other view libraries like React Native. A warning will be displayed when requiring the addons from this package. They will be removed in the next breaking change version. 51 | * `connectToStores`'s `getStateFromStores` signature has changed to `(context, props)`. 52 | 53 | ## 0.4.12 54 | 55 | ### Bug Fixes 56 | 57 | * [#197] Fixed issue where plugins were called with undefined during rehydration 58 | 59 | ## 0.4.11 60 | 61 | ### Bug Fixes 62 | 63 | * [#195] Fixed undefined error with rehydrating with an empty state object 64 | 65 | ### Features 66 | 67 | * [#196] Added `getStore` method to `FluxibleContext` class 68 | 69 | ## 0.4.10 70 | 71 | ### Bug Fixes 72 | 73 | * [#174] `MockActionContext.executeAction` now returns the result of the action properly - @ross-pfahler 74 | * [#175] Better protection for plugin's `plugContext` method returning undefined - @cesarandreu 75 | * [#176] Non-react specific statics on components will now be hoisted to the higher-order component wrappers 76 | 77 | ## 0.4.9 78 | 79 | ### Features 80 | 81 | * [#165] Export `fluxible/utils/MockActionContext` and `fluxible/utils/MockComponentContext` classes 82 | to make it easier to extend the classes for testing custom plugin functionality. 83 | 84 | ### Bug Fixes 85 | 86 | * [#164] Fixed the `connectToStores` functionality in minified environments as long as users specify a `storeName` static property. 87 | 88 | ## 0.4.8 89 | 90 | ### Bug Fixes 91 | 92 | * [#162] Fixed issue with 'catch' usage in IE<9 93 | 94 | ## 0.4.7 95 | 96 | ### Bug Fixes 97 | 98 | * [#156] Fixed issue with connectToStores calling setState after componentWillUnmount has been called 99 | 100 | ## 0.4.6 101 | 102 | ### Bug Fixes 103 | 104 | * [#144] Fixed issue with context prop not being provided to child of `FluxibleComponent` 105 | 106 | ## 0.4.5 107 | 108 | ### Bug Fixes 109 | 110 | * Fix undefined error on first `executeAction` in context 111 | 112 | ## 0.4.4 113 | 114 | ### Bug Fixes 115 | 116 | * [#141] Fixed `executeAction` not behaving asynchronously - @bruderstein 117 | * [#142] Fixed issue with `FluxibleComponent` double rendering - @antonklava 118 | 119 | ## 0.4.3 120 | 121 | ### Features 122 | 123 | * [#117] Add option to pass function to connectToStores which receives access to all stores 124 | 125 | ### Bug Fixes 126 | 127 | * [#120] Fixed component action handler errors being swallowed - @cesarandreu 128 | 129 | ## 0.4.2 130 | 131 | ### Bug Fixes 132 | 133 | * [#115] Moved factory warning to context.createElement since it's only relevant when using this method 134 | 135 | ## 0.4.1 136 | 137 | ### Bug Fixes 138 | 139 | * Fixed context plugin rehydration throwing undefined error 140 | 141 | ## 0.4.0 142 | 143 | ### New Features 144 | 145 | * [#69] `executeAction` and plugins now support Promises: returns Promise and allows Promise to be returned from action 146 | * [#70] `connectToStores` higher order component for listening to state changes 147 | * [#107] `provideContext` higher order component for setting child context on top level component 148 | * New `require('utils/createMockActionContext')` with simpler API to replace `MockActionContext` 149 | * New `require('utils/createMockComponentContext')` with simpler API to replace `MockComponentContext` 150 | 151 | ### Breaking Changes 152 | 153 | * removed `require('fluxible').Mixin` 154 | * removed `require('fluxible/utils/MockActionContext')` 155 | * removed `require('fluxible/utils/MockComponentContext')` 156 | 157 | ### Deprecations 158 | 159 | * [#100] It is no longer necessary to pass component factories to Fluxible constructor, instead just pass the component class 160 | * `require('fluxible').FluxibleComponent` has moved to `require('fluxible/addons/FluxibleComponent')` 161 | * `require('fluxible').FluxibleMixin` has moved to `require('fluxible/addons/FluxibleMixin')` 162 | 163 | ### Documentation 164 | 165 | * Moved addons to their own document and added recommendations on which ones to use when there are options 166 | 167 | ### Internals 168 | 169 | * Updated to dispatchr 0.3.x 170 | * Tests now use Babel instead of node-jsx 171 | 172 | ## 0.3.3 173 | 174 | ### Bug Fixes 175 | 176 | * [#89] Fixed unintentional payload coercion in `executeAction` 177 | 178 | ## 0.3.2 179 | 180 | ### Features 181 | 182 | * [#93] Allow context plugins' rehydrate method to be asynchronous 183 | 184 | ## 0.3.1 185 | 186 | ### Features 187 | 188 | * `React.createFactory` no longer needs to be called on your component before passing it in to Fluxible 189 | 190 | ## 0.3.0 191 | 192 | ### Features 193 | 194 | * `FluxibleContext.createElement()` now wraps component with `FluxibleComponent` to provide child context correctly 195 | 196 | ### Breaking Changes 197 | 198 | * React dependency pinned to 0.13 199 | 200 | ### Deprecations 201 | 202 | * `require('fluxible').Mixin` deprecated, moved to `require('fluxible').FluxibleMixin` 203 | * `require('fluxible/utils/BaseStore')` deprecated, moved to `require('fluxible/addons/BaseStore')` 204 | * `require('fluxible/utils/createStore')` deprecated, moved to `require('fluxible/addons/createStore')` 205 | 206 | ## 0.2.10 207 | 208 | ### Features 209 | 210 | * [#93] Allow context plugins' rehydrate method to be asynchronous 211 | 212 | ## 0.2.9 213 | 214 | ### Features 215 | 216 | * [#63] Added `FluxibleComponent` for wrapping app level component to provide child context 217 | 218 | ## 0.2.8 219 | 220 | ### Features 221 | 222 | * [#62] Support for React 0.13 223 | 224 | ## 0.2.6 - 0.2.7 225 | 226 | ### Features 227 | 228 | * [#58] Add ability to provide a component action handler in case errors get passed all the way up the action change 229 | 230 | ## 0.2.5 231 | 232 | ### Deprecations 233 | 234 | * `appComponent` renamed to `component` in Fluxible constructor 235 | 236 | ## 0.2.4 237 | 238 | ### Features 239 | 240 | * [#53] Use `action.displayName` if specified in debug logs (for debugging minified code) 241 | 242 | ## 0.2.3 243 | 244 | ### Features 245 | 246 | * [#52] `FluxibleMixin` now specifies `childContextTypes` and `getChildContext` in preparation for React 0.13 which will deprecate `React.withContext` 247 | 248 | ### Internals 249 | 250 | * [#47] devDependency upgrades 251 | 252 | ## 0.2.2 253 | 254 | ### Bug Fixes 255 | 256 | * Fix `MockComponentContext` not working with React context 257 | 258 | ## 0.2.1 259 | 260 | ### Features 261 | 262 | * Add `import {FluxibleMixin} from 'fluxible';` for better ES6 support instead of `import {Mixin} from 'fluxible';` 263 | 264 | ## 0.2.0 265 | 266 | ### Features 267 | 268 | * Full support for using React's context instead of passing `context` prop to all components 269 | 270 | ### Breaking Changes 271 | 272 | * `require('fluxible').StoreMixin` renamed to `require('fluxible').Mixin` since it includes more than just store listening 273 | 274 | ## 0.1.5 275 | 276 | ### Bug Fixes 277 | 278 | * [#22] Pass a `MockActionContext` to `MockComponentAction.executeAction` calls 279 | 280 | ## 0.1.4 281 | 282 | ### Bug Fixes 283 | 284 | * [#21] Pass noop to `MockActionContext.executeAction` to prevent `undefined is not a function` errors 285 | 286 | ## 0.1.3 287 | 288 | ### Internals 289 | 290 | * Update dependencies 291 | 292 | ## 0.1.2 293 | 294 | ### Internals 295 | 296 | * Update dependencies 297 | 298 | ## 0.1.1 299 | 300 | ### Bug Fixes 301 | 302 | * Fixed invalid require in `MockActionContext` 303 | 304 | ## 0.1.0 305 | 306 | First version. 307 | --------------------------------------------------------------------------------