Skip to content

Commit a4ce7c8

Browse files
mjacksonryanflorence
authored andcommitted
[changed] isActive is an instance method
[removed] <Routes onActiveStateChange> This commit removes ActiveStore (yay!). Instead, <Routes> components now store their own active state and emit active state change events to ActiveState descendants that are interested.
1 parent af1fb6e commit a4ce7c8

File tree

10 files changed

+236
-208
lines changed

10 files changed

+236
-208
lines changed

modules/components/Link.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,13 +119,13 @@ var Link = React.createClass({
119119
var params = Link.getParams(nextProps);
120120

121121
this.setState({
122-
isActive: Link.isActive(nextProps.to, params, nextProps.query)
122+
isActive: this.isActive(nextProps.to, params, nextProps.query)
123123
});
124124
},
125125

126126
updateActiveState: function () {
127127
this.setState({
128-
isActive: Link.isActive(this.props.to, Link.getParams(this.props), this.props.query)
128+
isActive: this.isActive(this.props.to, Link.getParams(this.props), this.props.query)
129129
});
130130
},
131131

modules/components/Routes.js

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ var DefaultLocation = require('../locations/DefaultLocation');
1111
var HashLocation = require('../locations/HashLocation');
1212
var HistoryLocation = require('../locations/HistoryLocation');
1313
var RefreshLocation = require('../locations/RefreshLocation');
14-
var ActiveStore = require('../stores/ActiveStore');
14+
var ActiveDelegate = require('../mixins/ActiveDelegate');
1515
var PathStore = require('../stores/PathStore');
1616
var RouteStore = require('../stores/RouteStore');
1717

@@ -43,13 +43,6 @@ function defaultAbortedTransitionHandler(transition) {
4343
}
4444
}
4545

46-
/**
47-
* The default handler for active state updates.
48-
*/
49-
function defaultActiveStateChangeHandler(state) {
50-
ActiveStore.updateState(state);
51-
}
52-
5346
/**
5447
* The default handler for errors that were thrown asynchronously
5548
* while transitioning. The default behavior is to re-throw the
@@ -74,9 +67,10 @@ var Routes = React.createClass({
7467

7568
displayName: 'Routes',
7669

70+
mixins: [ ActiveDelegate ],
71+
7772
propTypes: {
7873
onAbortedTransition: React.PropTypes.func.isRequired,
79-
onActiveStateChange: React.PropTypes.func.isRequired,
8074
onTransitionError: React.PropTypes.func.isRequired,
8175
preserveScrollPosition: React.PropTypes.bool,
8276
location: function (props, propName, componentName) {
@@ -90,7 +84,6 @@ var Routes = React.createClass({
9084
getDefaultProps: function () {
9185
return {
9286
onAbortedTransition: defaultAbortedTransitionHandler,
93-
onActiveStateChange: defaultActiveStateChangeHandler,
9487
onTransitionError: defaultTransitionErrorHandler,
9588
preserveScrollPosition: false,
9689
location: DefaultLocation
@@ -182,8 +175,7 @@ var Routes = React.createClass({
182175
if (transition.isAborted) {
183176
routes.props.onAbortedTransition(transition);
184177
} else if (nextState) {
185-
routes.setState(nextState);
186-
routes.props.onActiveStateChange(nextState);
178+
routes.setState(nextState, routes.emitChange);
187179

188180
// TODO: add functional test
189181
var rootMatch = getRootMatch(nextState.matches);

modules/mixins/ActiveDelegate.js

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
var React = require('react');
2+
var ChangeEmitter = require('./ChangeEmitter');
3+
4+
function routeIsActive(activeRoutes, routeName) {
5+
return activeRoutes.some(function (route) {
6+
return route.props.name === routeName;
7+
});
8+
}
9+
10+
function paramsAreActive(activeParams, params) {
11+
for (var property in params) {
12+
if (activeParams[property] !== String(params[property]))
13+
return false;
14+
}
15+
16+
return true;
17+
}
18+
19+
function queryIsActive(activeQuery, query) {
20+
for (var property in query) {
21+
if (activeQuery[property] !== String(query[property]))
22+
return false;
23+
}
24+
25+
return true;
26+
}
27+
28+
/**
29+
* A mixin for components that store the active state of routes, URL
30+
* parameters, and query.
31+
*/
32+
var ActiveDelegate = {
33+
34+
mixins: [ ChangeEmitter ],
35+
36+
childContextTypes: {
37+
activeDelegate: React.PropTypes.any.isRequired
38+
},
39+
40+
getChildContext: function () {
41+
return {
42+
activeDelegate: this
43+
};
44+
},
45+
46+
/**
47+
* Returns true if the route with the given name, URL parameters, and
48+
* query are all currently active.
49+
*/
50+
isActive: function (routeName, params, query) {
51+
var activeRoutes = this.state.activeRoutes || [];
52+
var activeParams = this.state.activeParams || {};
53+
var activeQuery = this.state.activeQuery || {};
54+
55+
var isActive = routeIsActive(activeRoutes, routeName) && paramsAreActive(activeParams, params);
56+
57+
if (query)
58+
return isActive && queryIsActive(activeQuery, query);
59+
60+
return isActive;
61+
}
62+
63+
};
64+
65+
module.exports = ActiveDelegate;

modules/mixins/ActiveState.js

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
1-
var ActiveStore = require('../stores/ActiveStore');
1+
var React = require('react');
2+
var ActiveDelegate = require('./ActiveDelegate');
23

34
/**
45
* A mixin for components that need to know about the routes, params,
56
* and query that are currently active. Components that use it get two
67
* things:
78
*
8-
* 1. An `isActive` static method they can use to check if a route,
9-
* params, and query are active.
10-
* 2. An `updateActiveState` instance method that is called when the
9+
* 1. An `updateActiveState` method that is called when the
1110
* active state changes.
11+
* 2. An `isActive` method they can use to check if a route,
12+
* params, and query are active.
13+
*
1214
*
1315
* Example:
1416
*
@@ -24,40 +26,45 @@ var ActiveStore = require('../stores/ActiveStore');
2426
*
2527
* updateActiveState: function () {
2628
* this.setState({
27-
* isActive: Tab.isActive(routeName, params, query)
29+
* isActive: this.isActive(routeName, params, query)
2830
* })
2931
* }
3032
*
3133
* });
3234
*/
3335
var ActiveState = {
3436

35-
statics: {
36-
37-
/**
38-
* Returns true if the route with the given name, URL parameters, and query
39-
* are all currently active.
40-
*/
41-
isActive: ActiveStore.isActive
42-
37+
contextTypes: {
38+
activeDelegate: React.PropTypes.any.isRequired
4339
},
4440

45-
componentWillMount: function () {
46-
ActiveStore.addChangeListener(this.handleActiveStateChange);
41+
/**
42+
* Returns this component's ActiveDelegate component.
43+
*/
44+
getActiveDelegate: function () {
45+
return this.context.activeDelegate;
4746
},
4847

4948
componentDidMount: function () {
50-
if (this.updateActiveState)
51-
this.updateActiveState();
49+
this.getActiveDelegate().addChangeListener(this.handleActiveStateChange);
50+
this.handleActiveStateChange();
5251
},
5352

5453
componentWillUnmount: function () {
55-
ActiveStore.removeChangeListener(this.handleActiveStateChange);
54+
this.getActiveDelegate().removeChangeListener(this.handleActiveStateChange);
5655
},
5756

5857
handleActiveStateChange: function () {
5958
if (this.isMounted() && typeof this.updateActiveState === 'function')
6059
this.updateActiveState();
60+
},
61+
62+
/**
63+
* Returns true if the route with the given name, URL parameters, and
64+
* query are all currently active.
65+
*/
66+
isActive: function (routeName, params, query) {
67+
return this.getActiveDelegate().isActive(routeName, params, query);
6168
}
6269

6370
};

modules/mixins/ChangeEmitter.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
var React = require('react');
2+
var EventEmitter = require('events').EventEmitter;
3+
4+
var CHANGE_EVENT = 'change';
5+
6+
/**
7+
* A mixin for components that emit change events. ActiveDelegate uses
8+
* this mixin to notify descendant ActiveState components when the
9+
* active state changes.
10+
*/
11+
var ChangeEmitter = {
12+
13+
propTypes: {
14+
maxChangeListeners: React.PropTypes.number.isRequired
15+
},
16+
17+
getDefaultProps: function () {
18+
return {
19+
maxChangeListeners: 0
20+
};
21+
},
22+
23+
componentWillMount: function () {
24+
this._events = new EventEmitter;
25+
this._events.setMaxListeners(this.props.maxChangeListeners);
26+
},
27+
28+
componentWillReceiveProps: function (nextProps) {
29+
this._events.setMaxListeners(nextProps.maxChangeListeners);
30+
},
31+
32+
componentWillUnmount: function () {
33+
this._events.removeAllListeners();
34+
},
35+
36+
addChangeListener: function (listener) {
37+
this._events.addListener(CHANGE_EVENT, listener);
38+
},
39+
40+
removeChangeListener: function (listener) {
41+
this._events.removeListener(CHANGE_EVENT, listener);
42+
},
43+
44+
emitChange: function () {
45+
this._events.emit(CHANGE_EVENT);
46+
}
47+
48+
};
49+
50+
module.exports = ChangeEmitter;

modules/stores/ActiveStore.js

Lines changed: 0 additions & 84 deletions
This file was deleted.

0 commit comments

Comments
 (0)