Skip to content

Commit a63c940

Browse files
mjacksonryanflorence
authored andcommitted
[added] <NotFoundRoute>
Fixes #140
1 parent d5bd656 commit a63c940

File tree

8 files changed

+124
-15
lines changed

8 files changed

+124
-15
lines changed

NotFoundRoute.js

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = require('./modules/components/NotFoundRoute');

examples/master-detail/app.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ var Route = Router.Route;
55
var DefaultRoute = Router.DefaultRoute;
66
var Routes = Router.Routes;
77
var Link = Router.Link;
8+
var NotFoundRoute = Router.NotFoundRoute;
89

910
var api = 'http://addressbook-api.herokuapp.com/contacts';
1011
var _contacts = {};
@@ -114,6 +115,7 @@ var App = React.createClass({
114115
<ul>
115116
{contacts}
116117
</ul>
118+
<Link to="/nothing-here">Invalid Link (not found)</Link>
117119
</div>
118120
<div className="Content">
119121
{this.props.activeRouteHandler()}
@@ -247,8 +249,8 @@ var routes = (
247249
<Route handler={App}>
248250
<DefaultRoute handler={Index}/>
249251
<Route name="new" path="contact/new" handler={NewContact}/>
250-
<Route name="not-found" path="contact/not-found" handler={NotFound}/>
251252
<Route name="contact" path="contact/:id" handler={Contact}/>
253+
<NotFoundRoute handler={NotFound}/>
252254
</Route>
253255
);
254256

index.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@ exports.ActiveState = require('./ActiveState');
22
exports.AsyncState = require('./AsyncState');
33
exports.DefaultRoute = require('./DefaultRoute');
44
exports.Link = require('./Link');
5+
exports.NotFoundRoute = require('./NotFoundRoute');
56
exports.Redirect = require('./Redirect');
67
exports.Route = require('./Route');
78
exports.Routes = require('./Routes');
89
exports.goBack = require('./goBack');
10+
exports.makeHref = require('./makeHref');
911
exports.replaceWith = require('./replaceWith');
1012
exports.transitionTo = require('./transitionTo');
11-
exports.makeHref = require('./makeHref');

modules/components/NotFoundRoute.js

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
var merge = require('react/lib/merge');
2+
var Route = require('./Route');
3+
4+
function NotFoundRoute(props) {
5+
return Route(
6+
merge(props, {
7+
path: null,
8+
catchAll: true
9+
})
10+
);
11+
}
12+
13+
module.exports = NotFoundRoute;

modules/components/Routes.js

+8-6
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ var Routes = React.createClass({
143143
* { route: <PostRoute>, params: { id: '123' } } ]
144144
*/
145145
match: function (path) {
146-
return findMatches(Path.withoutQuery(path), this.state.routes, this.props.defaultRoute);
146+
return findMatches(Path.withoutQuery(path), this.state.routes, this.props.defaultRoute, this.props.notFoundRoute);
147147
},
148148

149149
/**
@@ -218,14 +218,14 @@ var Routes = React.createClass({
218218

219219
});
220220

221-
function findMatches(path, routes, defaultRoute) {
221+
function findMatches(path, routes, defaultRoute, notFoundRoute) {
222222
var matches = null, route, params;
223223

224224
for (var i = 0, len = routes.length; i < len; ++i) {
225225
route = routes[i];
226226

227227
// Check the subtree first to find the most deeply-nested match.
228-
matches = findMatches(path, route.props.children, route.props.defaultRoute);
228+
matches = findMatches(path, route.props.children, route.props.defaultRoute, route.props.notFoundRoute);
229229

230230
if (matches != null) {
231231
var rootParams = getRootMatch(matches).params;
@@ -248,11 +248,13 @@ function findMatches(path, routes, defaultRoute) {
248248
}
249249

250250
// No routes matched, so try the default route if there is one.
251-
params = defaultRoute && Path.extractParams(defaultRoute.props.path, path);
252-
253-
if (params)
251+
if (defaultRoute && (params = Path.extractParams(defaultRoute.props.path, path)))
254252
return [ makeMatch(defaultRoute, params) ];
255253

254+
// Last attempt: does the "not found" route match?
255+
if (notFoundRoute && (params = Path.extractParams(notFoundRoute.props.path, path)))
256+
return [ makeMatch(notFoundRoute, params) ];
257+
256258
return matches;
257259
}
258260

modules/stores/RouteStore.js

+32-7
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,17 @@ var RouteStore = {
4848
props.name || props.path
4949
);
5050

51-
// Default routes have no name, path, or children.
52-
var isDefault = !(props.path || props.name || props.children);
53-
54-
if (props.path || props.name) {
51+
if ((props.path || props.name) && !props.catchAll) {
5552
props.path = Path.normalize(props.path || props.name);
56-
} else if (parentRoute && parentRoute.props.path) {
57-
props.path = parentRoute.props.path;
53+
} else if (parentRoute) {
54+
// <Routes> have no path prop.
55+
props.path = parentRoute.props.path || '/';
56+
57+
if (props.catchAll) {
58+
props.path += '*';
59+
} else if (!props.children) {
60+
props.isDefault = true;
61+
}
5862
} else {
5963
props.path = '/';
6064
}
@@ -85,7 +89,28 @@ var RouteStore = {
8589
_namedRoutes[props.name] = route;
8690
}
8791

88-
if (parentRoute && isDefault) {
92+
if (props.catchAll) {
93+
invariant(
94+
parentRoute,
95+
'<NotFoundRoute> must have a parent <Route>'
96+
);
97+
98+
invariant(
99+
parentRoute.props.notFoundRoute == null,
100+
'You may not have more than one <NotFoundRoute> per <Route>'
101+
);
102+
103+
parentRoute.props.notFoundRoute = route;
104+
105+
return null;
106+
}
107+
108+
if (props.isDefault) {
109+
invariant(
110+
parentRoute,
111+
'<DefaultRoute> must have a parent <Route>'
112+
);
113+
89114
invariant(
90115
parentRoute.props.defaultRoute == null,
91116
'You may not have more than one <DefaultRoute> per <Route>'

specs/NotFoundRoute.spec.js

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
require('./helper');
2+
var RouteStore = require('../modules/stores/RouteStore');
3+
var NotFoundRoute = require('../modules/components/NotFoundRoute');
4+
var Route = require('../modules/components/Route');
5+
var Routes = require('../modules/components/Routes');
6+
7+
var App = React.createClass({
8+
displayName: 'App',
9+
render: function () {
10+
return React.DOM.div();
11+
}
12+
});
13+
14+
describe('when registering a NotFoundRoute', function () {
15+
describe('nested inside a Route component', function () {
16+
it('becomes that Route\'s notFoundRoute', function () {
17+
var notFoundRoute;
18+
var route = Route({ handler: App },
19+
notFoundRoute = NotFoundRoute({ handler: App })
20+
);
21+
22+
RouteStore.registerRoute(route);
23+
expect(route.props.notFoundRoute).toBe(notFoundRoute);
24+
RouteStore.unregisterRoute(route);
25+
});
26+
});
27+
28+
describe('nested inside a Routes component', function () {
29+
it('becomes that Routes\' notFoundRoute', function () {
30+
var notFoundRoute;
31+
var routes = Routes({ handler: App },
32+
notFoundRoute = NotFoundRoute({ handler: App })
33+
);
34+
35+
RouteStore.registerRoute(notFoundRoute, routes);
36+
expect(routes.props.notFoundRoute).toBe(notFoundRoute);
37+
RouteStore.unregisterRoute(notFoundRoute);
38+
});
39+
});
40+
});
41+
42+
describe('when no child routes match a URL, but the beginning of the parent\'s path matches', function () {
43+
it('matches the default route', function () {
44+
var notFoundRoute;
45+
var routes = ReactTestUtils.renderIntoDocument(
46+
Routes(null,
47+
Route({ name: 'user', path: '/users/:id', handler: App },
48+
Route({ name: 'home', path: '/users/:id/home', handler: App }),
49+
// Make it the middle sibling to test order independence.
50+
notFoundRoute = NotFoundRoute({ handler: App }),
51+
Route({ name: 'news', path: '/users/:id/news', handler: App })
52+
)
53+
)
54+
);
55+
56+
var matches = routes.match('/users/5/not-found');
57+
assert(matches);
58+
expect(matches.length).toEqual(2);
59+
60+
expect(matches[1].route).toBe(notFoundRoute);
61+
62+
expect(matches[0].route.props.name).toEqual('user');
63+
});
64+
});

specs/main.js

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
require('./ActiveStore.spec.js');
44
require('./AsyncState.spec.js');
55
require('./DefaultRoute.spec.js');
6+
require('./NotFoundRoute.spec.js');
67
require('./Path.spec.js');
78
require('./PathStore.spec.js');
89
require('./Route.spec.js');

0 commit comments

Comments
 (0)