diff --git a/examples/containers/CounterApp.js b/examples/containers/CounterApp.js
index 79adc6c38b..b39823c0f9 100644
--- a/examples/containers/CounterApp.js
+++ b/examples/containers/CounterApp.js
@@ -1,5 +1,5 @@
import React from 'react';
-import { connect, bindActions } from 'redux';
+import { connect, bindActionCreators } from 'redux';
import Counter from '../components/Counter';
import * as CounterActions from '../actions/CounterActions';
@@ -8,10 +8,10 @@ import * as CounterActions from '../actions/CounterActions';
}))
export default class CounterApp {
render() {
- const { counter, dispatcher } = this.props;
+ const { counter, dispatch } = this.props;
return (
+ {...bindActionCreators(CounterActions, dispatch)} />
);
}
}
diff --git a/examples/containers/TodoApp.js b/examples/containers/TodoApp.js
index f952e2678f..9b66ca207f 100644
--- a/examples/containers/TodoApp.js
+++ b/examples/containers/TodoApp.js
@@ -1,5 +1,5 @@
import React from 'react';
-import { bindActions, Connector } from 'redux';
+import { bindActionCreators, Connector } from 'redux';
import AddTodo from '../components/AddTodo';
import TodoList from '../components/TodoList';
import * as TodoActions from '../actions/TodoActions';
@@ -13,8 +13,8 @@ export default class TodoApp {
);
}
- renderChild({ todos, dispatcher }) {
- const actions = bindActions(TodoActions, dispatcher);
+ renderChild({ todos, dispatch }) {
+ const actions = bindActionCreators(TodoActions, dispatch);
return (
diff --git a/src/Dispatcher.js b/src/Dispatcher.js
deleted file mode 100644
index 7f230bad0f..0000000000
--- a/src/Dispatcher.js
+++ /dev/null
@@ -1,59 +0,0 @@
-function dispatch(store, atom, action) {
- return store(atom, action);
-}
-
-export default class Dispatcher {
- constructor(store) {
- this.perform = this.perform.bind(this);
- this.store = store;
- this.initialize();
- }
-
- initialize({ atom, subscriptions = [] } = {}) {
- this.atom = atom;
- this.subscriptions = subscriptions;
- this.dispatch({});
- }
-
- dispose() {
- const { atom, subscriptions } = this;
- delete this.atom;
- this.subscriptions = [];
- return { atom, subscriptions };
- }
-
- dispatch(action) {
- const nextAtom = dispatch(this.store, this.atom, action);
- this.setAtom(nextAtom);
- }
-
- perform(action) {
- return typeof action === 'function'
- ? action(this.perform, this.atom)
- : this.dispatch(action);
- }
-
- getAtom() {
- return this.atom;
- }
-
- setAtom(atom) {
- this.atom = atom;
- this.emitChange();
- }
-
- subscribe(listener) {
- this.subscriptions.push(listener);
- listener(this.atom);
-
- return () => {
- const index = this.subscriptions.indexOf(listener);
- this.subscriptions.splice(index, 1);
- };
- }
-
- emitChange() {
- const { atom, subscriptions } = this;
- subscriptions.forEach(listener => listener(atom));
- }
-}
diff --git a/src/Redux.js b/src/Redux.js
new file mode 100644
index 0000000000..cd05470659
--- /dev/null
+++ b/src/Redux.js
@@ -0,0 +1,46 @@
+import createDispatcher from './createDispatcher';
+import composeStores from './utils/composeStores';
+
+export default class Redux {
+ constructor(dispatcher, initialState) {
+ if (typeof dispatcher === 'object') {
+ // A shortcut notation to use the default dispatcher
+ dispatcher = createDispatcher(composeStores(dispatcher));
+ }
+
+ this.state = initialState;
+ this.listeners = [];
+ this.replaceDispatcher(dispatcher);
+ }
+
+ getDispatcher() {
+ return this.dispatcher;
+ }
+
+ replaceDispatcher(nextDispatcher) {
+ this.dispatcher = nextDispatcher;
+ this.dispatchFn = nextDispatcher(this.state, ::this.setState);
+ }
+
+ dispatch(action) {
+ return this.dispatchFn(action);
+ }
+
+ getState() {
+ return this.state;
+ }
+
+ setState(nextState) {
+ this.state = nextState;
+ this.listeners.forEach(listener => listener());
+ }
+
+ subscribe(listener) {
+ this.listeners.push(listener);
+
+ return () => {
+ const index = this.listeners.indexOf(listener);
+ this.listeners.splice(index, 1);
+ };
+ }
+}
diff --git a/src/components/Connector.js b/src/components/Connector.js
index cf57585a47..1dab9334dd 100644
--- a/src/components/Connector.js
+++ b/src/components/Connector.js
@@ -37,13 +37,13 @@ export default class Connector extends Component {
this.handleChange = this.handleChange.bind(this);
this.unsubscribe = context.redux.subscribe(this.handleChange);
+ this.handleChange();
}
componentWillReceiveProps(nextProps) {
if (nextProps.select !== this.props.select) {
// Force the state slice recalculation
- const atom = this.context.redux.getAtom();
- this.handleChange(atom);
+ this.handleChange();
}
}
@@ -51,8 +51,10 @@ export default class Connector extends Component {
this.unsubscribe();
}
- handleChange(atom) {
- const slice = this.props.select(atom);
+ handleChange() {
+ const state = this.context.redux.getState();
+ const slice = this.props.select(state);
+
if (this.state) {
this.setState({ slice });
} else {
@@ -66,7 +68,7 @@ export default class Connector extends Component {
const { redux } = this.context;
return children({
- dispatcher: redux,
+ dispatch: ::redux.dispatch,
...slice
});
}
diff --git a/src/components/Provider.js b/src/components/Provider.js
index 06b6a07b25..3449f89eb6 100644
--- a/src/components/Provider.js
+++ b/src/components/Provider.js
@@ -1,49 +1,33 @@
-import { PropTypes } from 'react';
+import { Component, PropTypes } from 'react';
-const dispatcherShape = PropTypes.shape({
+const reduxShape = PropTypes.shape({
subscribe: PropTypes.func.isRequired,
- perform: PropTypes.func.isRequired,
- getAtom: PropTypes.func.isRequired
+ dispatch: PropTypes.func.isRequired,
+ getState: PropTypes.func.isRequired
});
-export default class Provider {
+export default class Provider extends Component {
static propTypes = {
- dispatcher: dispatcherShape.isRequired,
+ redux: reduxShape.isRequired,
children: PropTypes.func.isRequired
};
static childContextTypes = {
- redux: dispatcherShape.isRequired
+ redux: reduxShape.isRequired
};
getChildContext() {
- return { redux: this };
+ return { redux: this.state.redux };
}
- constructor() {
- this.dispatch = this.dispatch.bind(this);
+ constructor(props, context) {
+ super(props, context);
+ this.state = { redux: props.redux };
}
componentWillReceiveProps(nextProps) {
- nextProps.dispatcher.initialize(
- this.props.dispatcher.dispose()
- );
- }
-
- subscribe(listener) {
- return this.props.dispatcher.subscribe(listener);
- }
-
- dispatch(action) {
- return this.props.dispatcher.dispatch(action);
- }
-
- perform(actionCreator, ...args) {
- return this.props.dispatcher.perform(actionCreator, ...args);
- }
-
- getAtom() {
- return this.props.dispatcher.getAtom();
+ const nextDispatcher = nextProps.redux.getDispatcher();
+ this.state.redux.replaceDispatcher(nextDispatcher);
}
render() {
diff --git a/src/components/provide.js b/src/components/provide.js
index 0bb2b242ca..8f1195b3c2 100644
--- a/src/components/provide.js
+++ b/src/components/provide.js
@@ -2,13 +2,13 @@ import React from 'react';
import Provider from './Provider';
import getDisplayName from '../utils/getDisplayName';
-export default function provide(store) {
+export default function provide(redux) {
return DecoratedComponent => class ProviderDecorator {
static displayName = `Provider(${getDisplayName(DecoratedComponent)})`;
render() {
return (
-
+
{props => }
);
diff --git a/src/createDispatcher.js b/src/createDispatcher.js
index f0af386a85..b725dca3dd 100644
--- a/src/createDispatcher.js
+++ b/src/createDispatcher.js
@@ -1,14 +1,20 @@
-import Dispatcher from './Dispatcher';
+export default function createDispatcher(store) {
+ return function dispatcher(initialState, setState) {
+ let state = store(initialState, {});
+ setState(state);
-export default function createDispatcher(...args) {
- const dispatcher = new Dispatcher(...args);
+ function dispatchSync(action) {
+ state = store(state, action);
+ setState(state);
+ return action;
+ }
- return {
- subscribe: ::dispatcher.subscribe,
- perform: ::dispatcher.perform,
- getAtom: ::dispatcher.getAtom,
- setAtom: ::dispatcher.setAtom,
- initialize: ::dispatcher.initialize,
- dispose: ::dispatcher.dispose
+ function dispatch(action) {
+ return typeof action === 'function' ?
+ action(dispatch, state) :
+ dispatchSync(action);
+ }
+
+ return dispatch;
};
}
diff --git a/src/createRedux.js b/src/createRedux.js
new file mode 100644
index 0000000000..f3ee7c1347
--- /dev/null
+++ b/src/createRedux.js
@@ -0,0 +1,13 @@
+import Redux from './Redux';
+
+export default function createRedux(...args) {
+ const redux = new Redux(...args);
+
+ return {
+ subscribe: ::redux.subscribe,
+ dispatch: ::redux.dispatch,
+ getState: ::redux.getState,
+ getDispatcher: ::redux.getDispatcher,
+ replaceDispatcher: ::redux.replaceDispatcher
+ };
+}
diff --git a/src/index.js b/src/index.js
index 5a0febaf33..79fced8d89 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,4 +1,5 @@
// Core
+export createRedux from './createRedux';
export createDispatcher from './createDispatcher';
// Wrapper components
@@ -11,4 +12,4 @@ export connect from './components/connect';
// Utilities
export composeStores from './utils/composeStores';
-export bindActions from './utils/bindActions';
+export bindActionCreators from './utils/bindActionCreators';
diff --git a/src/utils/bindActionCreators.js b/src/utils/bindActionCreators.js
new file mode 100644
index 0000000000..801665492c
--- /dev/null
+++ b/src/utils/bindActionCreators.js
@@ -0,0 +1,7 @@
+import mapValues from 'lodash/object/mapValues';
+
+export default function bindActionCreators(actionCreators, dispatch) {
+ return mapValues(actionCreators, actionCreator =>
+ (...args) => dispatch(actionCreator(...args))
+ );
+}
diff --git a/src/utils/bindActions.js b/src/utils/bindActions.js
deleted file mode 100644
index c7f3a2321f..0000000000
--- a/src/utils/bindActions.js
+++ /dev/null
@@ -1,7 +0,0 @@
-import mapValues from 'lodash/object/mapValues';
-
-export default function bindActions(actionCreators, dispatcher) {
- return mapValues(actionCreators, actionCreator =>
- (...args) => dispatcher.perform(actionCreator(...args))
- );
-}