Skip to content

Commit 5fea685

Browse files
committed
[added] Router.AsyncState mixin
Helps with #57
1 parent b0aa491 commit 5fea685

File tree

5 files changed

+179
-0
lines changed

5 files changed

+179
-0
lines changed

modules/helpers/resolveAsyncState.js

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
var Promise = require('es6-promise').Promise;
2+
3+
/**
4+
* Resolves all values in the given stateDescription object
5+
* and calls the setState function with new state as they resolve.
6+
*/
7+
function resolveAsyncState(stateDescription, setState) {
8+
if (stateDescription == null)
9+
return Promise.resolve({});
10+
11+
var keys = Object.keys(stateDescription);
12+
13+
return Promise.all(
14+
keys.map(function (key) {
15+
return Promise.resolve(stateDescription[key]).then(function (value) {
16+
var newState = {};
17+
newState[key] = value;
18+
setState(newState);
19+
});
20+
})
21+
);
22+
}
23+
24+
module.exports = resolveAsyncState;

modules/main.js

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ exports.replaceWith = require('./helpers/replaceWith');
77
exports.transitionTo = require('./helpers/transitionTo');
88

99
exports.ActiveState = require('./mixins/ActiveState');
10+
exports.AsyncState = require('./mixins/AsyncState');
1011

1112
// Backwards compat with 0.1. We should
1213
// remove this when we ship 1.0.

modules/mixins/AsyncState.js

+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
var React = require('react');
2+
var resolveAsyncState = require('../helpers/resolveAsyncState');
3+
4+
/**
5+
* A mixin for route handler component classes that fetch at least
6+
* part of their state asynchronously. Classes that use it should
7+
* declare a static `getInitialAsyncState` method that fetches state
8+
* for a component after it mounts. This function is given three
9+
* arguments: 1) the current route params, 2) the current query and
10+
* 3) a function that can be used to set state as it is received.
11+
*
12+
* Example:
13+
*
14+
* var User = React.createClass({
15+
*
16+
* statics: {
17+
*
18+
* getInitialAsyncState: function (params, query, setState) {
19+
* // If you don't need to do anything async, just update
20+
* // the state immediately and you're done.
21+
* setState({
22+
* user: UserStore.getUserByID(params.userID)
23+
* });
24+
*
25+
* // Or, ignore the setState argument entirely and return a
26+
* // hash with keys named after the state variables you want
27+
* // to set. The values may be immediate values or promises.
28+
* return {
29+
* user: getUserByID(params.userID) // may be a promise
30+
* };
31+
*
32+
* // Or, stream your data!
33+
* var buffer = '';
34+
*
35+
* return {
36+
*
37+
* // Same as above, the stream state variable is set to the
38+
* // value returned by this promise when it resolves.
39+
* stream: getStreamingData(params.userID, function (chunk) {
40+
* buffer += chunk;
41+
*
42+
* // Notify of progress.
43+
* setState({
44+
* streamBuffer: buffer
45+
* });
46+
* })
47+
*
48+
* };
49+
* }
50+
*
51+
* },
52+
*
53+
* getInitialState: function () {
54+
* return {
55+
* user: null, // Receives a value when getUserByID resolves.
56+
* stream: null, // Receives a value when getStreamingData resolves.
57+
* streamBuffer: '' // Used to track data as it loads.
58+
* };
59+
* },
60+
*
61+
* render: function () {
62+
* if (!this.state.user)
63+
* return <LoadingUser/>;
64+
*
65+
* return (
66+
* <div>
67+
* <p>Welcome {this.state.user.name}!</p>
68+
* <p>So far, you've received {this.state.streamBuffer.length} data!</p>
69+
* </div>
70+
* );
71+
* }
72+
*
73+
* });
74+
*
75+
* When testing, use the `initialAsyncState` prop to simulate asynchronous
76+
* data fetching. When this prop is present, no attempt is made to retrieve
77+
* additional state via `getInitialAsyncState`.
78+
*/
79+
var AsyncState = {
80+
81+
propTypes: {
82+
initialAsyncState: React.PropTypes.object
83+
},
84+
85+
getInitialState: function () {
86+
return this.props.initialAsyncState || null;
87+
},
88+
89+
updateAsyncState: function (state) {
90+
if (this.isMounted())
91+
this.setState(state);
92+
},
93+
94+
componentDidMount: function () {
95+
if (this.props.initialAsyncState || !this.constructor.getInitialAsyncState)
96+
return;
97+
98+
resolveAsyncState(
99+
this.constructor.getInitialAsyncState(this.props.params, this.props.query, this.updateAsyncState),
100+
this.updateAsyncState
101+
);
102+
}
103+
104+
};
105+
106+
module.exports = AsyncState;

specs/AsyncState.spec.js

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
require('./helper');
2+
var Promise = require('es6-promise').Promise;
3+
var AsyncState = require('../modules/mixins/AsyncState');
4+
5+
describe('AsyncState', function () {
6+
7+
8+
describe('a component that fetches part of its state asynchronously', function () {
9+
it('resolves all state variables correctly', function (done) {
10+
var User = React.createClass({
11+
mixins: [ AsyncState ],
12+
statics: {
13+
getInitialAsyncState: function (params, query, setState) {
14+
setState({
15+
immediateValue: 'immediate'
16+
});
17+
18+
setTimeout(function () {
19+
setState({
20+
delayedValue: 'delayed'
21+
});
22+
});
23+
24+
return {
25+
promisedValue: Promise.resolve('promised')
26+
};
27+
}
28+
},
29+
render: function () {
30+
return null;
31+
}
32+
});
33+
34+
var user = TestUtils.renderIntoDocument(
35+
User()
36+
);
37+
38+
setTimeout(function () {
39+
expect(user.state.immediateValue).toEqual('immediate');
40+
expect(user.state.delayedValue).toEqual('delayed');
41+
expect(user.state.promisedValue).toEqual('promised');
42+
done();
43+
}, 20);
44+
});
45+
});
46+
47+
});

specs/main.js

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// TODO: this is for webkpack-karma to only create one build instead of a build
22
// for every spec file, there must be some sort of config but I can't find it ...
33
require('./ActiveStore.spec.js');
4+
require('./AsyncState.spec.js');
45
require('./Path.spec.js');
56
require('./Route.spec.js');
67
require('./RouteStore.spec.js');

0 commit comments

Comments
 (0)