React + Backbone Router
Last time we looked at how to use state to control the rendering and behaviour of components. Today we’ll take a look at how to use React with Backbone’s router.
Backbone is great for organising your application, but React is much cooler than Backbone’s View system. We’re going to display components at different routes and even transition between the routes, using CSS transitions.
You can find the accompanying source-code, for this tutorial, at: https://github.com/formativ/tutorial-react-backbone-router.
Please post any errors you find, or questions you have as comments on this article or as issues on GitHub.
Thanks to Ryan Sorensen for helping to improve this!
Using jQuery
One of the requirements for Backbone is jQuery. jQuery is a general purpose JavaScript library which specialises is DOM manipulation. It’s not a hard dependency of Backbone, but it’s tricky to run Backbone without it.
Using jQuery is as simple as downloading the JavaScript files and including them in your web pages. You can find the downloads at: http://jquery.com/download.
If you’re concerned about being able to support older browsers, then you’re better off trying version 1.x. Version 2.x is designed to work on modern browsers, that don’t require a ton of hacks and legacy code to render modern interfaces.
Using Underscore
Underscore is another of Backbone’s dependencies. It’s a utility library, full of functional programming support. Unlike jQuery, Underscore is a hard dependency which would be difficult Backbone without.
Using Underscore can also be achieved by downloading the JavaScript files and including them in your web pages. You can find the downloads at: http://underscorejs.org.
Using Backbone
With the dependencies in place, we can use Backbone! You can download it at: http://backbonejs.org. Include the JavaScript files in your web pages (as usual).
Using Backbone Router
It’s easy to use Backbone’s router. You just need to register a number of routes and callback functions for them:
var Router = Backbone.Router.extend({
routes : {
"" : "index",
"foo" : "foo",
"bar" : "bar"
},
index : function() {
console.log("index");
},
foo : function() {
console.log("foo");
},
bar : function() {
console.log("bar");
}
});
new Router();
Backbone.history.start();
We start by extending the Backbone.Router object, and defining three routes: index, foo and bar. These correspond with the callbacks which will log messages to console when they are routed to. Fire up your browser and navigate between these routes by changing the hash value (#, page.html#foo and page.html#bar).
You can find out more about the router at: http://backbonejs.org/#Router.
Route-Based Components
Adding components on top of this isn’t too tricky. There are a few approaches we can take, so we’ll start with repeated component mounting:
/**
* @jsx React.DOM
*/
var FooComponent = React.createClass({
render : function() {
return <div>foo</div>;
}
});
var BarComponent = React.createClass({
render : function() {
return <div>bar</div>;
}
});
var Router = Backbone.Router.extend({
routes : {
"foo" : "foo",
"bar" : "bar"
},
foo : function() {
React.renderComponent(
<FooComponent />,
document.body
);
},
bar : function() {
React.renderComponent(
<BarComponent />,
document.body
);
}
});
new Router();
Backbone.history.start();
As each callback is routed, the corresponding component is mounted to the DOM. This is a feasible approach to conditionally displaying components, but it does mean a bit of repeated code.
Route-Aware Components
The next approach to conditional component display involves using the router as a source of truth, and listening to its events:
/**
* @jsx React.DOM
*/
var FooComponent = React.createClass({
render : function() {
return <div>foo</div>;
}
});
var BarComponent = React.createClass({
render : function() {
return <div>bar</div>;
}
});
var InterfaceComponent = React.createClass({
componentWillMount : function() {
this.callback = (function() {
this.forceUpdate();
}).bind(this);
this.props.router.on("route", this.callback);
},
componentWillUnmount : function() {
this.props.router.off("route", this.callback);
},
render : function() {
if (this.props.router.current == "foo") {
return <FooComponent />;
}
if (this.props.router.current == "bar") {
return <BarComponent />;
}
return <div />;
}
});
var Router = Backbone.Router.extend({
routes : {
"foo" : "foo",
"bar" : "bar"
},
foo : function() {
this.current = "foo";
},
bar : function() {
this.current = "bar";
}
});
var router = new Router();
React.renderComponent(
<InterfaceComponent router={router} />,
document.body
);
Backbone.history.start();
This time we pass the router to our InterfaceComponent and listen to it’s route event. This will fire when the route is changed, and we can then call the forceUpdate() method to reflect the change.
Component Transitions
Adding and removing components gets us most of the way to a Backbone routed interface, but it would be great to have some transitions in there to make the experience less jarring. For that, we need to extend the previous example a little:
/**
* @jsx React.DOM
*/
var RouterMixin = {
componentWillMount : function() {
this.callback = (function() {
this.forceUpdate();
}).bind(this);
this.props.router.on("route", this.callback);
},
componentWillUnmount : function() {
this.props.router.off("route", this.callback);
}
};
var FooComponent = React.createClass({
mixins : [RouterMixin],
handle : function() {
this.props.router.navigate("bar", {
trigger : true
});
},
render : function() {
var className = "animate-leave animate-leave-active";
if (this.props.router.current == "foo") {
className = "animate-enter animate-enter-active";
}
return (
<div className={className}>
in foo,
<a onClick={this.handle}>go to bar</a>
</div>
);
}
});
var BarComponent = React.createClass({
mixins : [RouterMixin],
handle : function() {
this.props.router.navigate("foo", {
trigger : true
});
},
render : function() {
var className = "animate-leave animate-leave-active";
if (this.props.router.current == "bar") {
className = "animate-enter animate-enter-active";
}
return (
<div className={className}>
in bar,
<a onClick={this.handle}>go to foo</a>
</div>
);
}
});
var InterfaceComponent = React.createClass({
mixins : [RouterMixin],
render : function() {
var router = this.props.router;
return (
<div>
<FooComponent router={router} />
<BarComponent router={router} />
</div>
);
}
});
var Router = Backbone.Router.extend({
routes : {
"foo" : "foo",
"bar" : "bar"
},
foo : function() {
this.current = "foo";
},
bar : function() {
this.current = "bar";
}
});
var router = new Router();
React.renderComponent(
<InterfaceComponent router={router} />,
document.body
);
Backbone.history.start();
In this example, all of our components are router-aware. We’re able to share this functionality by using a mixin for each of the components. The FooComponent and BarComponent classes adjust their classes, based on which of them should be displayed for the current route.
We also use the router’s navigate() method to publish a route change event (the same one we listen for) and update the URL. These links makes the interface a little easier to navigate and provide a good example of how to attach click events to React components.
We also need some CSS to make them transition:
.animate-enter {
opacity : 0.01;
transition : opacity .5s ease-in;
}
.animate-enter.animate-enter-active {
opacity : 1;
}
.animate-leave {
opacity : 1;
transition : opacity .5s ease-in;
}
.animate-leave.animate-leave-active {
opacity : 0.01;
}
a {
cursor : pointer;
color : blue;
}
Conclusion
React and Backbone’s router make a killer combo when it comes to interfaces responding to URL changes. It gets even better with the addition of other Backbone components…
If you enjoyed this tutorial; it would be helpful if you could click the Recommend button and share it with other developers.