Skip to content

Commit 8562482

Browse files
committed
[added] ActiveState mixin
Components can mixin Router.ActiveState to receive notifications when the active routes, params, and query change. Fixes #85
1 parent 616f8bf commit 8562482

File tree

5 files changed

+135
-71
lines changed

5 files changed

+135
-71
lines changed

modules/components/Link.js

+9-23
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
var React = require('react');
2-
var ActiveStore = require('../stores/ActiveStore');
2+
var ActiveState = require('../mixins/ActiveState');
33
var withoutProperties = require('../helpers/withoutProperties');
44
var transitionTo = require('../helpers/transitionTo');
55
var makeHref = require('../helpers/makeHref');
@@ -31,8 +31,11 @@ var RESERVED_PROPS = {
3131
* <Link to="showPost" postId="123" query={{show:true}}/>
3232
*/
3333
var Link = React.createClass({
34+
3435
displayName: 'Link',
3536

37+
mixins: [ ActiveState ],
38+
3639
statics: {
3740

3841
getUnreservedProps: function (props) {
@@ -86,34 +89,17 @@ var Link = React.createClass({
8689
return className;
8790
},
8891

89-
componentWillMount: function () {
90-
ActiveStore.addChangeListener(this.handleActiveChange);
91-
},
92-
93-
componentDidMount: function () {
94-
this.updateActive();
95-
},
96-
97-
componentWillUnmount: function () {
98-
ActiveStore.removeChangeListener(this.handleActiveChange);
99-
},
100-
101-
componentWillReceiveProps: function(props) {
102-
var params = Link.getUnreservedProps(props);
92+
componentWillReceiveProps: function (nextProps) {
93+
var params = Link.getUnreservedProps(nextProps);
10394

10495
this.setState({
105-
isActive: ActiveStore.isActive(props.to, params, props.query)
96+
isActive: Link.isActive(nextProps.to, params, nextProps.query)
10697
});
10798
},
10899

109-
handleActiveChange: function () {
110-
if (this.isMounted())
111-
this.updateActive();
112-
},
113-
114-
updateActive: function () {
100+
updateActiveState: function () {
115101
this.setState({
116-
isActive: ActiveStore.isActive(this.props.to, this.getParams(), this.props.query)
102+
isActive: Link.isActive(this.props.to, this.getParams(), this.props.query)
117103
});
118104
},
119105

modules/components/Route.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ var Route = React.createClass({
194194
if (transition.isCancelled) {
195195
Route.handleCancelledTransition(transition, route);
196196
} else if (newState) {
197-
ActiveStore.update(newState);
197+
ActiveStore.updateState(newState);
198198
}
199199

200200
return transition;

modules/main.js

+2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ exports.goBack = require('./helpers/goBack');
55
exports.replaceWith = require('./helpers/replaceWith');
66
exports.transitionTo = require('./helpers/transitionTo');
77

8+
exports.ActiveState = require('./mixins/ActiveState');
9+
810
// Backwards compat with 0.1. We should
911
// remove this when we ship 1.0.
1012
exports.Router = require('./Router');

modules/mixins/ActiveState.js

+97
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
var ActiveStore = require('../stores/ActiveStore');
2+
3+
function routeIsActive(activeRoutes, routeName) {
4+
return activeRoutes.some(function (route) {
5+
return route.props.name === routeName;
6+
});
7+
}
8+
9+
function paramsAreActive(activeParams, params) {
10+
for (var property in params) {
11+
if (activeParams[property] !== String(params[property]))
12+
return false;
13+
}
14+
15+
return true;
16+
}
17+
18+
function queryIsActive(activeQuery, query) {
19+
for (var property in query) {
20+
if (activeQuery[property] !== String(query[property]))
21+
return false;
22+
}
23+
24+
return true;
25+
}
26+
27+
/**
28+
* A mixin for components that need to know about the routes, params,
29+
* and query that are currently active. Components that use it get two
30+
* things:
31+
*
32+
* 1. An `isActive` static method they can use to check if a route,
33+
* params, and query are active.
34+
* 2. An `updateActiveState` instance method that is called when the
35+
* active state changes.
36+
*
37+
* Example:
38+
*
39+
* var Tab = React.createClass({
40+
*
41+
* mixins: [ Router.ActiveState ],
42+
*
43+
* getInitialState: function () {
44+
* return {
45+
* isActive: false
46+
* };
47+
* },
48+
*
49+
* updateActiveState: function () {
50+
* this.setState({
51+
* isActive: Tab.isActive(routeName, params, query)
52+
* })
53+
* }
54+
*
55+
* });
56+
*/
57+
var ActiveState = {
58+
59+
statics: {
60+
61+
/**
62+
* Returns true if the route with the given name, URL parameters, and query
63+
* are all currently active.
64+
*/
65+
isActive: function (routeName, params, query) {
66+
var state = ActiveStore.getState();
67+
var isActive = routeIsActive(state.routes, routeName) && paramsAreActive(state.params, params);
68+
69+
if (query)
70+
return isActive && queryIsActive(state.query, query);
71+
72+
return isActive;
73+
}
74+
75+
},
76+
77+
componentWillMount: function () {
78+
ActiveStore.addChangeListener(this.handleActiveStateChange);
79+
},
80+
81+
componentDidMount: function () {
82+
if (this.updateActiveState)
83+
this.updateActiveState();
84+
},
85+
86+
componentWillUnmount: function () {
87+
ActiveStore.removeChangeListener(this.handleActiveStateChange);
88+
},
89+
90+
handleActiveStateChange: function () {
91+
if (this.isMounted() && this.updateActiveState)
92+
this.updateActiveState();
93+
}
94+
95+
};
96+
97+
module.exports = ActiveState;

modules/stores/ActiveStore.js

+26-47
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,7 @@
11
var _activeRoutes = [];
2-
3-
function routeIsActive(routeName) {
4-
return _activeRoutes.some(function (route) {
5-
return route.props.name === routeName;
6-
});
7-
}
8-
92
var _activeParams = {};
10-
11-
function paramsAreActive(params) {
12-
for (var property in params) {
13-
if (_activeParams[property] !== String(params[property]))
14-
return false;
15-
}
16-
17-
return true;
18-
}
19-
203
var _activeQuery = {};
214

22-
function queryIsActive(query) {
23-
for (var property in query) {
24-
if (_activeQuery[property] !== String(query[property]))
25-
return false;
26-
}
27-
28-
return true;
29-
}
30-
315
var EventEmitter = require('event-emitter');
326
var _events = EventEmitter();
337

@@ -42,27 +16,6 @@ function notifyChange() {
4216
*/
4317
var ActiveStore = {
4418

45-
update: function (state) {
46-
state = state || {};
47-
_activeRoutes = state.activeRoutes || [];
48-
_activeParams = state.activeParams || {};
49-
_activeQuery = state.activeQuery || {};
50-
notifyChange();
51-
},
52-
53-
/**
54-
* Returns true if the route with the given name, URL parameters, and query
55-
* are all currently active.
56-
*/
57-
isActive: function (routeName, params, query) {
58-
var isActive = routeIsActive(routeName) && paramsAreActive(params);
59-
60-
if (query)
61-
isActive = isActive && queryIsActive(query);
62-
63-
return isActive;
64-
},
65-
6619
/**
6720
* Adds a listener that will be called when this store changes.
6821
*/
@@ -75,6 +28,32 @@ var ActiveStore = {
7528
*/
7629
removeChangeListener: function (listener) {
7730
_events.off('change', listener);
31+
},
32+
33+
/**
34+
* Updates the currently active state and notifies all listeners.
35+
* This is automatically called by routes as they become active.
36+
*/
37+
updateState: function (state) {
38+
state = state || {};
39+
40+
_activeRoutes = state.activeRoutes || [];
41+
_activeParams = state.activeParams || {};
42+
_activeQuery = state.activeQuery || {};
43+
44+
notifyChange();
45+
},
46+
47+
/**
48+
* Returns an object with the currently active `routes`, `params`,
49+
* and `query`.
50+
*/
51+
getState: function () {
52+
return {
53+
routes: _activeRoutes,
54+
params: _activeParams,
55+
query: _activeQuery
56+
};
7857
}
7958

8059
};

0 commit comments

Comments
 (0)