[Update]
After working on a bunch of React/flux applications, I've come to the conclusion that I prefer for routing to be handled separately and orthogonally to flux. The strategy is that the URL/routes should determine which components get mounted, and the components request data from the stores based on the route parameters and other application state as necessary.
[Original Answer]
An approach I took with a recent project while experimenting with Flux was to make the routing layer just another store. This means that all links that change the URL actually trigger an action through the dispatcher requesting that the route be updated. A RouteStore
responded to this dispatch by setting the URL in the browser and setting some internal data (via route-recognizer) so that the views could query the new routing data upon the change
event being fired from the store.
One non-obvious piece for me was how to ensure URL changes triggered actions; I ended up creating a mixin to manage this for me (note: this isn't 100% robust, but worked for the app I was using; you may have to make modifications to suit your needs).
// Mix-in to the top-level component to capture `click`
// events on all links and turn them into action dispatches;
// also manage HTML5 history via pushState/popState
var RoutingMixin = {
componentDidMount: function() {
// Some browsers have some weirdness with firing an extra 'popState'
// right when the page loads
var firstPopState = true;
// Intercept all bubbled click events on the app's element
this.getDOMNode().addEventListener('click', this._handleRouteClick);
window.onpopstate = function(e) {
if (firstPopState) {
firstPopState = false;
return;
}
var path = document.location.toString().replace(document.location.origin, '');
this.handleRouteChange(path, true);
}.bind(this);
},
componentWillUnmount: function() {
this.getDOMNode().removeEventListener('click', this._handleRouteClick);
window.onpopstate = null;
},
_handleRouteClick: function(e) {
var target = e.target;
// figure out if we clicked on an `a` tag
while(target && target.tagName !== 'A') {
target = target.parentNode;
}
if (!target) return;
// if the user was holding a modifier key, don't intercept
if (!e.altKey && !e.ctrlKey && !e.shiftKey && !e.metaKey) {
e.preventDefault();
var href = target.attributes.href.value;
this.handleRouteChange(href, false);
}
}
};
It would be used as so:
var ApplicationView = React.createClass({
mixins: [RoutingMixin],
handleRouteChange: function(newUrl, fromHistory) {
this.dispatcher.dispatch(RouteActions.changeUrl(newUrl, fromHistory));
},
// ...
});
The handler in the store might look something like:
RouteStore.prototype.handleChangeUrl = function(href, skipHistory) {
var isFullUrl = function(url) {
return url.indexOf('http://') === 0 || url.indexOf('https://') === 0;
}
// links with a protocol simply change the location
if (isFullUrl(href)) {
document.location = href;
} else {
// this._router is a route-recognizer instance
var results = this._router.recognize(href);
if (results && results.length) {
var route = results[0].handler(href, results[0].params);
this.currentRoute = route;
if (!skipHistory) history.pushState(href, '', href);
}
this.emit("change");
}
}