Skip to content

Commit

Permalink
Don't use getAtom(), pass state on each dispatch instead
Browse files Browse the repository at this point in the history
  • Loading branch information
acdlite committed Jun 8, 2015
1 parent 38f4a16 commit 532672e
Show file tree
Hide file tree
Showing 8 changed files with 142 additions and 37 deletions.
26 changes: 26 additions & 0 deletions examples/components/Transactions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React, { PropTypes } from 'react';
import DOMify from 'react-domify';

export default class Transactions {
static propTypes = {
status: PropTypes.object.isRequired,
commit: PropTypes.func.isRequired,
begin: PropTypes.func.isRequired,
rollback: PropTypes.func.isRequired
};

render() {
const { status, commit, begin, rollback } = this.props;
return (
<p>
<DOMify value={status} />
{' '}
<button onClick={begin}>Begin</button>
{' '}
<button onClick={commit}>Commit</button>
{' '}
<button onClick={rollback}>Rollback</button>
</p>
);
}
}
17 changes: 12 additions & 5 deletions examples/containers/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from 'react';
import { createDispatcher, Provider, composeStores, compose } from 'redux';
import CounterApp from './CounterApp';
import TodoApp from './TodoApp';
import TransactionsApp from './TransactionsApp';
import * as stores from '../stores/index';
import createTransactor from '../middleware/createTransactor';
import callbackMiddleware from 'redux/middleware/callback';
Expand All @@ -17,11 +18,16 @@ function promiseMiddleware(next) {

const store = composeStores(stores);
const transactor = createTransactor();
const dispatcher = createDispatcher(getAtom => compose(
promiseMiddleware,
callbackMiddleware,
transactor(getAtom, store)
));
// const dispatcher = createDispatcher(compose(
// promiseMiddleware,
// callbackMiddleware,
// transactor(store)
// ));
const dispatcher = createDispatcher({
middleware: compose(promiseMiddleware, callbackMiddleware),
reducer: transactor(store)
});


global.transactor = transactor;

Expand All @@ -33,6 +39,7 @@ export default class App {
<div>
<CounterApp />
<TodoApp />
<TransactionsApp transactor={transactor} />
</div>
}
</Provider>
Expand Down
36 changes: 36 additions & 0 deletions examples/containers/TransactionsApp.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import React, { PropTypes } from 'react';
import { Connector } from 'redux';
import Transactions from '../components/Transactions';

const transactorShape = PropTypes.shape({
getStatus: PropTypes.func.isRequired,
begin: PropTypes.func.isRequired,
commit: PropTypes.func.isRequired,
rollback: PropTypes.func.isRequired
});

export default class TransactionsApp {
static propTypes = {
transactor: transactorShape.isRequired
};

render() {
return (
// TODO: Need a way to subscribe to all dispatches, without memoization
<Connector select={() => ({ lol: {} })}>
{::this.renderChild}
</Connector>
);
}

renderChild() {
const { transactor: { getStatus, begin, commit, rollback } } = this.props;

return (
<Transactions status={getStatus()}
commit={commit}
begin={begin}
rollback={rollback} />
);
}
}
79 changes: 51 additions & 28 deletions examples/middleware/createTransactor.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,45 @@
function noop() {}

class Transactor {
inProgress = false;
actions = [];

middleware(getAtom, store) {
middleware(store) {
this.store = store;
this.getAtom = getAtom;
this.headState = getAtom();

return next => {
this.next = next;

return action => {
if (this.inProgress) {
this.actions.push(action);
return next(this.reduceActions(this.headState, this.actions));
} else {
const state = this.store(this.getAtom(), action);
this.headState = state;
return next(state);
}

return (state, dispatch) => {
this.dispatch = dispatch;
this.currentState = state;

if (this.isCapturingState) {
this.isCapturingState = false;
return () => () => this.next(state);
}

return next => {
this.next = next;

return action => {
if (this.inProgress) {
this.actions.push(action);
const nextState = this.reduceActions(this.headState, this.actions);
return this.next(nextState);
} else {
const nextState = this.store(this.currentState, action);
return this.next(nextState);
}
};
};
};
}

// Get the current state atom by running a dummy dispatch.
getCurrentState() {
this.isCapturingState = true;
this.dispatch();
return this.currentState;
}

reduceActions(initialState, actions) {
return actions.reduce(
(state, action) => this.store(state, action),
Expand All @@ -31,24 +48,30 @@ class Transactor {
}

begin() {
this.inProgress = true;
this.dirty = false;
this.actions = [];
if (!this.inProgress) {
this.inProgress = true;
this.actions = [];
this.headState = this.getCurrentState();
}
}

commit() {
this.inProgress = false;
this.dirty = false;
this.actions = [];
this.headState = this.getAtom();
if (this.inProgress) {
this.inProgress = false;
this.actions = [];
delete this.headState;

This comment has been minimized.

Copy link
@ooflorent

ooflorent Jun 8, 2015

Contributor

Don't do that. It will mutate the hidden class.

This comment has been minimized.

Copy link
@acdlite

acdlite Jun 8, 2015

Author Collaborator

Hmm, I don't get it... where else should the state be stored?

This comment has been minimized.

Copy link
@rpominov

rpominov Jun 8, 2015

Just don't delete, set to undefined instead.

This comment has been minimized.

Copy link
@ooflorent

ooflorent Jun 8, 2015

Contributor

Or null

this.next(this.getCurrentState());
}
}

rollback() {
const { headState } = this;
this.inProgress = false;
this.dirty = false;
this.actions = [];
return this.next(headState);
if (this.inProgress) {
this.inProgress = false;
this.actions = [];
this.currentState = this.headState;
delete this.headState;
return this.next(this.currentState);
}
}

getStatus() {
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"babel-loader": "^5.1.2",
"eslint": "^0.22.1",
"eslint-plugin-react": "^2.3.0",
"react-domify": "~0.2.1",
"react-hot-loader": "^1.2.7",
"rimraf": "^2.3.4",
"webpack": "^1.9.6",
Expand Down
15 changes: 11 additions & 4 deletions src/Dispatcher.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import compose from './utils/compose';

export default class Dispatcher {
constructor(middleware) {
this.middleware = middleware(::this.getAtom);
constructor({ reducer, middleware }) {
this.reducer = reducer;
this.middleware = middleware;
this.initialize();
}

Expand All @@ -17,8 +20,12 @@ export default class Dispatcher {
return { atom, subscriptions };
}

dispatch(action) {
this.middleware(nextAtom => this.setAtom(nextAtom))(action);
dispatch = (action) => {
this.middleware(
_action => this.reducer(this.getAtom(), this.dispatch)(
nextAtom => this.setAtom(nextAtom)
)(_action)
)(action);
}

getAtom() {
Expand Down
4 changes: 4 additions & 0 deletions src/createReducer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export default function createReducer(store) {
return state => next => action =>
next(store(state, action));
}
1 change: 1 addition & 0 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// Core
export createDispatcher from './createDispatcher';
export createReducer from './createReducer';

// Wrapper components
export Provider from './components/Provider';
Expand Down

0 comments on commit 532672e

Please sign in to comment.