From eee86f8c2ef63df0c64ebf7ef11f229426cfb3dc Mon Sep 17 00:00:00 2001 From: Kyle Warren Date: Tue, 15 Mar 2016 13:17:40 -0700 Subject: [PATCH 1/8] removed react hmre and added flow --- .babelrc | 9 +------- .flowconfig | 3 +++ package.json | 9 +++++--- src/client.js | 51 +++++++++++++++++++++++++++++++++------------- src/stores/base.js | 14 +++++++++---- 5 files changed, 57 insertions(+), 29 deletions(-) create mode 100644 .flowconfig diff --git a/.babelrc b/.babelrc index 78de745..acc67d1 100644 --- a/.babelrc +++ b/.babelrc @@ -3,13 +3,6 @@ "es2015", "react" ], - "compact": true, - "env": { - "hmr": { - "presets": [ - "react-hmre" - ] - } - } + "compact": true } diff --git a/.flowconfig b/.flowconfig new file mode 100644 index 0000000..d00a128 --- /dev/null +++ b/.flowconfig @@ -0,0 +1,3 @@ +[ignore] +.*/node_modules/react/node_modules/.* + diff --git a/package.json b/package.json index 9072613..937a652 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,8 @@ "start": "node build/server/index.js", "start:dev": "NODE_ENV=development node_modules/nodemon/bin/nodemon.js build/server/index.js", "start:hot": "HMR=true npm run start:dev", - "hot": "NODE_ENV=development BABEL_ENV=hmr node webpack-dev-server.js" + "hot": "NODE_ENV=development node webpack-dev-server.js", + "flow": "flow" }, "license": "MIT", "author": { @@ -37,6 +38,7 @@ "webpack" ], "devDependencies": { + "flow-bin": "^0.22.1", "nodemon": "^1.8.1", "webpack-dev-server": "^1.14.1" }, @@ -46,12 +48,12 @@ "babel-loader": "^6.2.1", "babel-preset-es2015": "^6.3.13", "babel-preset-react": "^6.3.13", - "babel-preset-react-hmre": "^1.0.1", "css-loader": "^0.23.1", + "deep-freeze": "^0.0.1", "express": "^4.13.3", "extract-text-webpack-plugin": "^1.0.1", "jade": "^1.11.0", - "lodash": "^3.10.1", + "lodash": "^4.6.1", "null-loader": "^0.1.1", "postcss-import": "^8.0.2", "postcss-loader": "^0.8.0", @@ -60,6 +62,7 @@ "react-dom": "^0.14.7", "react-router": "^2.0.0-rc4", "rimraf": "^2.5.1", + "sculpt": "git@github.com:malectro/sculpt", "serialize-javascript": "^1.1.2", "serve-favicon": "^2.3.0", "source-map-support": "^0.4.0", diff --git a/src/client.js b/src/client.js index 5aac124..19ff101 100644 --- a/src/client.js +++ b/src/client.js @@ -1,28 +1,51 @@ import React from 'react'; -import { render } from 'react-dom'; -import { Router, browserHistory, match } from 'react-router'; +import {render} from 'react-dom'; +import {Router, browserHistory, match} from 'react-router'; import IndexStore from 'src/stores/index'; import routes from 'src/routes'; -import { getDependencies } from 'src/utils/index'; -import { FluxContext } from 'src/utils/wrappers'; +import {getDependencies} from 'src/utils/index'; +import {FluxContext} from 'src/utils/wrappers'; -const store = new IndexStore(); - +let store = new IndexStore(); store.initialize(window.data); +let currentRoutes = routes; + // Get route dependencies whenever a route component is rendered. const routeHandler = (Component, props) => { getDependencies([props.route], store, props.params); - return + return ; +}; + +function renderAll() { + match({currentRoutes, location}, () => { + render(( + + + + ), document.getElementById('app')); + }); } -match({routes, location }, () => { - render(( - - - - ), document.getElementById('app')); -}) +renderAll(); + + +if (module.hot) { + module.hot.accept('src/routes', () => { + currentRoutes = require('src/routes').default; + renderAll(); + }); + + module.hot.accept('src/stores/index', () => { + const NewIndexStore = require('src/stores/index'); + const data = store.serialize(); + + store = new NewIndexStore(); + store.initialize(data); + + renderAll(); + }); +} diff --git a/src/stores/base.js b/src/stores/base.js index 59b108b..00abf12 100644 --- a/src/stores/base.js +++ b/src/stores/base.js @@ -1,6 +1,7 @@ import _ from 'lodash'; +import deepFreeze from 'deep-freeze'; import EventEmitter from 'events'; -import update from 'react-addons-update'; +import sculpt from 'sculpt'; export default class Store extends EventEmitter { @@ -11,15 +12,20 @@ export default class Store extends EventEmitter { } initialize(data) { - this.state = data[this.key]; + this.state = deepFreeze(data[this.key]); } getState() { - return _.cloneDeep(this.state); + return this.state; } setState(data) { - this.state = this.state ? update(this.state, {$merge: data}) : data; + this.state = this.state ? sculpt(this.state, {$merge: data}) : deepFreeze(data); + this.emit('update'); + } + + updateState(spec) { + this.state = sculpt(this.state, spec); this.emit('update'); } From 2f2731fb1846db712e1f87cef65674b13b84ac3e Mon Sep 17 00:00:00 2001 From: Kyle Warren Date: Tue, 15 Mar 2016 13:40:25 -0700 Subject: [PATCH 2/8] removed code splitting --- src/routes.js | 59 +++++++++------------------------------------------ 1 file changed, 10 insertions(+), 49 deletions(-) diff --git a/src/routes.js b/src/routes.js index 53c6d0c..08d68ce 100644 --- a/src/routes.js +++ b/src/routes.js @@ -4,6 +4,10 @@ import { Route, IndexRoute } from 'react-router'; import App from 'src/components/app.jsx'; import Dashboard from 'src/components/dashboard/dashboard.jsx'; +import About from 'src/components/about/about.jsx'; +import Things from 'src/components/things/things.jsx'; +import ListOfThings from 'src/components/list/list.jsx'; +import Thing from 'src/components/thing/thing.jsx'; import * as actions from 'src/actions/index'; @@ -11,56 +15,13 @@ import * as actions from 'src/actions/index'; if (typeof require.ensure !== 'function') require.ensure = (d, c) => c(require) export default ( - { - require.ensure([], (require) => { - cb(null, [ - { - path: 'about', - getComponent(location, cb) { - require.ensure([], () => { - cb(null, require('src/components/about/about.jsx').default); - }); - } - }, - { - path: 'list', - getComponent(location, cb) { - require.ensure([], () => { - cb(null, require('src/components/things/things.jsx').default); - }); - }, - dependencies: actions.getIds, - getChildRoutes(location, cb) { - require.ensure([], (require) => { - cb(null, [ - { - path: 'thing/:id', - getComponent(location, cb) { - require.ensure([], () => { - cb(null, require('src/components/thing/thing.jsx').default); - }); - }, - dependencies: actions.getThing, - } - ]); - }); - }, - getIndexRoute(location, cb) { - require.ensure([], (require) => { - cb(null, { - getComponent(location, cb) { - require.ensure([], () => { - cb(null, require('src/components/list/list.jsx').default); - }); - } - }); - }); - } - } - ]); - }); - }}> + + + + + + ); From 40aaf46e425719c8dd0ea89cb5149f1cfa6d8515 Mon Sep 17 00:00:00 2001 From: Kyle Warren Date: Tue, 15 Mar 2016 14:21:26 -0700 Subject: [PATCH 3/8] added cold reloading to the router --- src/client.js | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/client.js b/src/client.js index 19ff101..1cf128d 100644 --- a/src/client.js +++ b/src/client.js @@ -8,8 +8,17 @@ import {getDependencies} from 'src/utils/index'; import {FluxContext} from 'src/utils/wrappers'; +let data = localStorage.data; +if (data) { + localStorage.data = null; + data = JSON.parse(data); +} else { + data = window.data; +} + + let store = new IndexStore(); -store.initialize(window.data); +store.initialize(data); let currentRoutes = routes; @@ -21,9 +30,10 @@ const routeHandler = (Component, props) => { function renderAll() { match({currentRoutes, location}, () => { + console.log('rendering'); render(( - + ), document.getElementById('app')); }); @@ -34,8 +44,8 @@ renderAll(); if (module.hot) { module.hot.accept('src/routes', () => { - currentRoutes = require('src/routes').default; - renderAll(); + localStorage.data = store.serialize(); + location.refresh(); }); module.hot.accept('src/stores/index', () => { From 36f8fea2574fc04ab87875ad95984cd5df570a38 Mon Sep 17 00:00:00 2001 From: Kyle Warren Date: Tue, 15 Mar 2016 14:32:42 -0700 Subject: [PATCH 4/8] finished hot reloading --- src/client.js | 18 +++++------------- src/components/app.jsx | 2 +- src/stores/base.js | 3 ++- 3 files changed, 8 insertions(+), 15 deletions(-) diff --git a/src/client.js b/src/client.js index 1cf128d..3c8efa3 100644 --- a/src/client.js +++ b/src/client.js @@ -1,5 +1,5 @@ import React from 'react'; -import {render} from 'react-dom'; +import ReactDOM, {render} from 'react-dom'; import {Router, browserHistory, match} from 'react-router'; import IndexStore from 'src/stores/index'; @@ -8,15 +8,6 @@ import {getDependencies} from 'src/utils/index'; import {FluxContext} from 'src/utils/wrappers'; -let data = localStorage.data; -if (data) { - localStorage.data = null; - data = JSON.parse(data); -} else { - data = window.data; -} - - let store = new IndexStore(); store.initialize(data); @@ -30,7 +21,6 @@ const routeHandler = (Component, props) => { function renderAll() { match({currentRoutes, location}, () => { - console.log('rendering'); render(( @@ -44,8 +34,10 @@ renderAll(); if (module.hot) { module.hot.accept('src/routes', () => { - localStorage.data = store.serialize(); - location.refresh(); + // NOTE (kyle): i'm not sure if this is sound, but we'll never run HMR on prod + currentRoutes = require('src/routes').default; + ReactDOM.unmountComponentAtNode(document.getElementById('app')); + renderAll(); }); module.hot.accept('src/stores/index', () => { diff --git a/src/components/app.jsx b/src/components/app.jsx index 049614b..7328068 100644 --- a/src/components/app.jsx +++ b/src/components/app.jsx @@ -10,7 +10,7 @@ export default class App extends React.Component { return (
- Hi world! + Hello world!! About Dashboard Things diff --git a/src/stores/base.js b/src/stores/base.js index 00abf12..5633e62 100644 --- a/src/stores/base.js +++ b/src/stores/base.js @@ -12,7 +12,8 @@ export default class Store extends EventEmitter { } initialize(data) { - this.state = deepFreeze(data[this.key]); + const state = data[this.key]; + this.state = state ? deepFreeze(state) : state; } getState() { From 06dd1d847862317e6a3c09069dba981b2dab8aa3 Mon Sep 17 00:00:00 2001 From: Kyle Warren Date: Tue, 15 Mar 2016 15:34:37 -0700 Subject: [PATCH 5/8] enforced immutability --- src/client.js | 2 +- src/components/list/list.jsx | 2 +- src/server.js | 4 ++-- src/stores/base.js | 2 +- src/stores/cache.js | 5 +++-- src/stores/ids.js | 5 +++++ src/stores/index.js | 10 +++++++--- 7 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/client.js b/src/client.js index 3c8efa3..40aa63b 100644 --- a/src/client.js +++ b/src/client.js @@ -41,7 +41,7 @@ if (module.hot) { }); module.hot.accept('src/stores/index', () => { - const NewIndexStore = require('src/stores/index'); + const NewIndexStore = require('src/stores/index').default; const data = store.serialize(); store = new NewIndexStore(); diff --git a/src/components/list/list.jsx b/src/components/list/list.jsx index 3895329..24f896b 100644 --- a/src/components/list/list.jsx +++ b/src/components/list/list.jsx @@ -8,7 +8,7 @@ import { subscribeToStore } from 'src/utils/wrappers'; class List extends React.Component { render() { - const things = this.context.store.stores.ids.getState() || []; + const things = this.context.store.stores.ids.getOddThings() || []; return (
{ things.map(thing => ( diff --git a/src/server.js b/src/server.js index 25dc2d1..e70a91d 100644 --- a/src/server.js +++ b/src/server.js @@ -69,8 +69,8 @@ app.get('/*', function(req, res) { base: HOT_MODULE_REPLACEMENT ? 'http://localhost:8080' : '', }); }) - .catch((error) => { - console.log(error); + .catch(error => { + console.error(error.stack || error); res.status(404).send('Not found'); }); } else { diff --git a/src/stores/base.js b/src/stores/base.js index 5633e62..d9babc2 100644 --- a/src/stores/base.js +++ b/src/stores/base.js @@ -21,7 +21,7 @@ export default class Store extends EventEmitter { } setState(data) { - this.state = this.state ? sculpt(this.state, {$merge: data}) : deepFreeze(data); + this.state = this.state ? sculpt(this.state, {$assign: data}) : deepFreeze(data); this.emit('update'); } diff --git a/src/stores/cache.js b/src/stores/cache.js index 623e206..ff5d253 100644 --- a/src/stores/cache.js +++ b/src/stores/cache.js @@ -1,4 +1,5 @@ import _ from 'lodash'; +import {set} from 'sculpt'; export default class Cache { @@ -11,7 +12,7 @@ export default class Cache { } set(key) { - this.cache[key] = new Date().getTime(); + this.cache = set(this.cache, key, new Date().getTime()); } expired(key, ttl) { @@ -22,7 +23,7 @@ export default class Cache { const now = new Date().getTime(); if (now - cached > ttl) { - delete this.cache[key]; + this.cache = _.omit(this.cache, key); return true; } diff --git a/src/stores/ids.js b/src/stores/ids.js index 05142f2..22ef274 100644 --- a/src/stores/ids.js +++ b/src/stores/ids.js @@ -1,8 +1,13 @@ +import _ from 'lodash'; import Store from './base'; export default class IdStore extends Store { constructor() { super('ids'); } + + getOddThings() { + return this.state.filter(id => id % 2); + } } diff --git a/src/stores/index.js b/src/stores/index.js index f9b72fc..c8a4818 100644 --- a/src/stores/index.js +++ b/src/stores/index.js @@ -1,6 +1,6 @@ import _ from 'lodash'; import EventEmitter from 'events'; -import update from 'react-addons-update'; +import sculpt from 'sculpt'; import IdStore from './ids'; import ThingStore from './things'; @@ -35,12 +35,16 @@ class IndexStore extends EventEmitter { let data = _.reduce(this.stores, (state, store) => { const storeState = store.serialize(); if (storeState) { - state = update(state, {$merge: storeState}); + state = sculpt(state, {$assign: storeState}); } return state; }, {}); - data._cache = this.cache.serialize(); + data = sculpt(data, { + _cache: { + $set: this.cache.serialize(), + }, + }); return data; } From e56fcbb13755039653c2c148724ac11fd9646b6c Mon Sep 17 00:00:00 2001 From: Kyle Warren Date: Tue, 15 Mar 2016 16:09:43 -0700 Subject: [PATCH 6/8] fixed hot replacement for stores added ability to cache store getters --- src/client.js | 5 +++++ src/stores/base.js | 15 +++++++++++++++ src/stores/cache.js | 8 ++++++++ src/stores/ids.js | 2 ++ 4 files changed, 30 insertions(+) diff --git a/src/client.js b/src/client.js index 40aa63b..14214e3 100644 --- a/src/client.js +++ b/src/client.js @@ -37,7 +37,9 @@ if (module.hot) { // NOTE (kyle): i'm not sure if this is sound, but we'll never run HMR on prod currentRoutes = require('src/routes').default; ReactDOM.unmountComponentAtNode(document.getElementById('app')); + store.cache.setPerma(true); renderAll(); + store.cache.setPerma(false); }); module.hot.accept('src/stores/index', () => { @@ -47,7 +49,10 @@ if (module.hot) { store = new NewIndexStore(); store.initialize(data); + ReactDOM.unmountComponentAtNode(document.getElementById('app')); + store.cache.setPerma(true); renderAll(); + store.cache.setPerma(false); }); } diff --git a/src/stores/base.js b/src/stores/base.js index d9babc2..aee1fe7 100644 --- a/src/stores/base.js +++ b/src/stores/base.js @@ -9,11 +9,13 @@ export default class Store extends EventEmitter { super(); this.key = key; this.state = null; + this._selections = new WeakMap(); } initialize(data) { const state = data[this.key]; this.state = state ? deepFreeze(state) : state; + this._selections = new WeakMap(); } getState() { @@ -21,12 +23,15 @@ export default class Store extends EventEmitter { } setState(data) { + // TODO (kyle): i'm not sure $merg'ing here is expected behavior this.state = this.state ? sculpt(this.state, {$assign: data}) : deepFreeze(data); + this._selections = new WeakMap(); this.emit('update'); } updateState(spec) { this.state = sculpt(this.state, spec); + this._selections = new WeakMap(); this.emit('update'); } @@ -35,5 +40,15 @@ export default class Store extends EventEmitter { [this.key]: this.getState(), }; } + + createSelector(method) { + return function () { + const result = this._selections[method]; + if (result) { + return result; + } + return this._selections[method] = method.apply(this, arguments); + } + } } diff --git a/src/stores/cache.js b/src/stores/cache.js index ff5d253..e5edb03 100644 --- a/src/stores/cache.js +++ b/src/stores/cache.js @@ -16,6 +16,10 @@ export default class Cache { } expired(key, ttl) { + if (this.__perma) { + return false; + } + const cached = this.cache[key]; if (!cached) { return true; @@ -33,5 +37,9 @@ export default class Cache { serialize() { return this.cache; } + + setPerma(perma) { + this.__perma = perma; + } } diff --git a/src/stores/ids.js b/src/stores/ids.js index 22ef274..75a153c 100644 --- a/src/stores/ids.js +++ b/src/stores/ids.js @@ -4,6 +4,8 @@ import Store from './base'; export default class IdStore extends Store { constructor() { super('ids'); + + this.getOddThings = this.createSelector(this.getOddThings); } getOddThings() { From e5dcc564fb9973af4a2679ceb90b99ae280c71d1 Mon Sep 17 00:00:00 2001 From: Kyle Warren Date: Tue, 15 Mar 2016 16:19:10 -0700 Subject: [PATCH 7/8] replaced fluxcontext with fluxroot --- src/client.js | 6 +++--- src/flux/component.jsx | 24 ++++++++++++++++++++++ src/flux/root.jsx | 46 ++++++++++++++++++++++++++++++++++++++++++ src/server.js | 6 +++--- src/utils/wrappers.js | 31 ---------------------------- 5 files changed, 76 insertions(+), 37 deletions(-) create mode 100644 src/flux/component.jsx create mode 100644 src/flux/root.jsx diff --git a/src/client.js b/src/client.js index 14214e3..81b9f29 100644 --- a/src/client.js +++ b/src/client.js @@ -5,7 +5,7 @@ import {Router, browserHistory, match} from 'react-router'; import IndexStore from 'src/stores/index'; import routes from 'src/routes'; import {getDependencies} from 'src/utils/index'; -import {FluxContext} from 'src/utils/wrappers'; +import FluxRoot from 'src/flux/root.jsx'; let store = new IndexStore(); @@ -22,9 +22,9 @@ const routeHandler = (Component, props) => { function renderAll() { match({currentRoutes, location}, () => { render(( - + - + ), document.getElementById('app')); }); } diff --git a/src/flux/component.jsx b/src/flux/component.jsx new file mode 100644 index 0000000..1c6b93b --- /dev/null +++ b/src/flux/component.jsx @@ -0,0 +1,24 @@ +/* @flow */ + +import React from 'react'; + +import typeof Store from 'stores/index'; + + +export default function(Component: Object): Function { + const FluxComponent = (props, context) => { + const { store } = context; + const fluxProps: { store: Store; ref?: Function } = { + store, + }; + if (props.fluxRef) { + fluxProps.ref = props.fluxRef; + } + return ; + } + FluxComponent.contextTypes = { + store: React.PropTypes.object, + }; + return FluxComponent; +}; + diff --git a/src/flux/root.jsx b/src/flux/root.jsx new file mode 100644 index 0000000..dae0c7e --- /dev/null +++ b/src/flux/root.jsx @@ -0,0 +1,46 @@ +import React from 'react'; + + +/** + * Top-level component that provides flux objects via context to all children. + * + * Should be used as a wrapper around any routing to provide the flux objects + * to the React subtree. + * + * Based on Redux's Provider component: + * https://github.com/rackt/react-redux/blob/master/src/components/Provider.js + * + * NOTE: Only supplies the store in this implementation but could be updated + * to provide other flux-related objects if necessary. + */ +export default class FluxRoot extends React.Component { + constructor(props, context) { + super(props, context); + this.store = props.store; + } + + getChildContext() { + return { + store: this.store, + }; + } + + componentDidMount() { + this.store.on('update', () => { + this.forceUpdate(); + }); + } + + componentWillUnmount() { + this.store.off(null, null, this); + } + + render() { + return this.props.children; + } +} + +FluxRoot.childContextTypes = { + store: React.PropTypes.object, +}; + diff --git a/src/server.js b/src/server.js index e70a91d..d39504a 100644 --- a/src/server.js +++ b/src/server.js @@ -10,7 +10,7 @@ import routes from './routes'; import IndexStore from './stores/index'; import { getDependencies } from './utils/index'; -import { FluxContext } from './utils/wrappers'; +import FluxRoot from './flux/root.jsx'; const DEVELOPMENT = process.env.NODE_ENV === 'development'; @@ -57,9 +57,9 @@ app.get('/*', function(req, res) { Promise.all(dependencies) .then(() => { const content = renderToString(( - + - + )); const data = serialize(store.serialize()); res.render('index', { diff --git a/src/utils/wrappers.js b/src/utils/wrappers.js index fab1757..cf3fc4f 100644 --- a/src/utils/wrappers.js +++ b/src/utils/wrappers.js @@ -45,34 +45,3 @@ export function subscribeToStore(Component) { }); }; - -/** - * Top-level component that provides flux objects via context to all children. - * - * Should be used as a wrapper around any routing to provide the flux objects - * to the React subtree. - * - * Based on Redux's Provider component: - * https://github.com/rackt/react-redux/blob/master/src/components/Provider.js - * - * NOTE: Only supplies the store in this implementation but could be updated - * to provide other flux-related objects if necessary. - */ -export class FluxContext extends React.Component { - getChildContext() { - return { store: this.store }; - } - - constructor(props, context) { - super(props, context); - this.store = props.store; - } - - render() { - return this.props.children; - } -} -FluxContext.childContextTypes = { - store: React.PropTypes.object, -} - From 6c1182771533fb892e27c4c5fdc94980993d7088 Mon Sep 17 00:00:00 2001 From: Kyle Warren Date: Tue, 15 Mar 2016 16:31:26 -0700 Subject: [PATCH 8/8] fixed fluxroot and fluxcomponent --- src/client.js | 1 + src/components/list/list.jsx | 6 ++--- src/components/thing/thing.jsx | 8 +++--- src/flux/root.jsx | 7 +++-- src/utils/wrappers.js | 47 ---------------------------------- 5 files changed, 11 insertions(+), 58 deletions(-) delete mode 100644 src/utils/wrappers.js diff --git a/src/client.js b/src/client.js index 81b9f29..fea4ab1 100644 --- a/src/client.js +++ b/src/client.js @@ -56,3 +56,4 @@ if (module.hot) { }); } +window.store = store; diff --git a/src/components/list/list.jsx b/src/components/list/list.jsx index 24f896b..f434636 100644 --- a/src/components/list/list.jsx +++ b/src/components/list/list.jsx @@ -3,12 +3,12 @@ import styles from './list.css'; import React from 'react'; import { Link } from 'react-router'; -import { subscribeToStore } from 'src/utils/wrappers'; +import FluxComponent from 'src/flux/component.jsx'; class List extends React.Component { render() { - const things = this.context.store.stores.ids.getOddThings() || []; + const things = this.props.store.stores.ids.getOddThings() || []; return (
{ things.map(thing => ( @@ -23,5 +23,5 @@ class List extends React.Component { } -export default subscribeToStore(List); +export default FluxComponent(List); diff --git a/src/components/thing/thing.jsx b/src/components/thing/thing.jsx index 451255c..c4bcb05 100644 --- a/src/components/thing/thing.jsx +++ b/src/components/thing/thing.jsx @@ -2,22 +2,22 @@ import styles from './thing.css'; import React from 'react'; -import { subscribeToStore } from 'src/utils/wrappers'; +import FluxComponent from 'src/flux/component.jsx'; class Thing extends React.Component { render() { const id = parseInt(this.props.params.id, 10); - const things = this.context.store.stores.things.getState(); + const things = this.props.store.stores.things.getState(); const thing = things && things[id] || {}; return (
- Thing { thing.id } : { thing.text } + Things { thing.id } : { thing.text }
); } } -export default subscribeToStore(Thing); +export default FluxComponent(Thing); diff --git a/src/flux/root.jsx b/src/flux/root.jsx index dae0c7e..1892235 100644 --- a/src/flux/root.jsx +++ b/src/flux/root.jsx @@ -26,13 +26,12 @@ export default class FluxRoot extends React.Component { } componentDidMount() { - this.store.on('update', () => { - this.forceUpdate(); - }); + this.listener = () => this.forceUpdate(); + this.store.on('update', this.listener); } componentWillUnmount() { - this.store.off(null, null, this); + this.store.removeListener('update', this.listener); } render() { diff --git a/src/utils/wrappers.js b/src/utils/wrappers.js deleted file mode 100644 index cf3fc4f..0000000 --- a/src/utils/wrappers.js +++ /dev/null @@ -1,47 +0,0 @@ -import React from 'react'; - - -// Note: This module is used to create higher order components as an -// alternative to using mixins, based on the pattern here: -// https://medium.com/@dan_abramov/ -// mixins-are-dead-long-live-higher-order-components-94a0d2f9e750 - - -/** - * Re-render the given Component whenever the store updates and add the - * store to the Component's context (e.g. `this.context.store`). - * - * @param {Component} Component to re-render on store updates. - * - * @return {Component} Higher order component that will re-render on store - * updates. - */ -export function subscribeToStore(Component) { - // Add store to component's context (via `this.context.store`). - Component.contextTypes = { - store: React.PropTypes.object, - }; - - return React.createClass({ - contextTypes: { - store: React.PropTypes.object, - }, - - componentDidMount() { - this.context.store.on('update', this.rerender); - }, - - componentWillUnmount() { - this.context.store.removeListener('update', this.rerender); - }, - - rerender() { - this.forceUpdate(); - }, - - render() { - return ; - } - }); -}; -