Skip to content

Commit 50d4d38

Browse files
committed
feat(catch-all): handled mapping the catch-all route to a 404 status
1 parent c48d04b commit 50d4d38

File tree

12 files changed

+70
-22
lines changed

12 files changed

+70
-22
lines changed

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ export default {
4545
<IndexRoute component={Index}/>
4646
<Route path="/foo" component={Foo}/>
4747
<Route path="/bar" component={Bar}/>
48+
<Route path="*" component={NotFound}/>
4849
</Route>
4950
),
5051
Root: ({store, children}) => (
@@ -69,6 +70,10 @@ are currently not provided, so these dependencies are required.
6970
additional steps before the response
7071
* `routes`: the definition of your react-router routes that this plugin should match the request url
7172
against
73+
* If you use a [catch-all route](https://github.com/ReactTraining/react-router/blob/c3cd9675bd8a31368f87da74ac588981cbd6eae7/upgrade-guides/v1.0.0.md#notfound-route)
74+
to display an appropriate message when the route does not match, it should have a `displayName` of `NotFound`. This
75+
will enable the status code to be passed to `respond` as `404`. Please note that the automatic mapping of the `name`
76+
property should not be relied on because it can be mangled during minification and, therefore, not match in production.
7277
* `Root`: a react component that will wrap the mounted components that result from the matched route
7378
* `store`: a data store that will be passed as a prop to the `<Root />` component so that your
7479
component can inject it into the context through a provider component.

example/components/not-found.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import React from 'react';
2+
3+
export default function NotFound() {
4+
return (
5+
<div>
6+
<h1>404</h1>
7+
<p>Page Not Found</p>
8+
</div>
9+
);
10+
}
11+
12+
NotFound.displayName = 'NotFound';

example/manifest.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ export default {
4141
{
4242
plugin: {
4343
register: '../src/route',
44-
options: {respond, routes, Root, store: createStore(() => undefined)}
44+
options: {respond, routes, Root, configureStore: () => createStore(() => undefined)}
4545
}
4646
}
4747
]

example/respond.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
export default function respond(reply, {renderedContent}) {
1+
export default function respond(reply, {renderedContent, status}) {
22
reply.view('layout', {
33
renderedContent,
44
title: '<title>Example Title</title>'
5-
});
5+
}).code(status);
66
}

example/routes.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@ import Wrap from './components/wrap';
55
import Index from './components/index';
66
import Foo from './components/foo';
77
import Bar from './components/bar';
8+
import NotFound from './components/not-found';
89

9-
const routes = (
10+
export default (
1011
<Route path="/" component={Wrap}>
1112
<IndexRoute component={Index} />
1213
<Route path="/foo" component={Foo} />
1314
<Route path="/bar" component={Bar} />
15+
<Route path="*" component={NotFound} />
1416
</Route>
1517
);
16-
17-
export default routes;

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,5 +94,8 @@
9494
"commitizen": {
9595
"path": "./node_modules/cz-conventional-changelog"
9696
}
97+
},
98+
"dependencies": {
99+
"http-status-codes": "1.0.6"
97100
}
98101
}

src/data-fetcher.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import {trigger} from 'redial';
22

3-
export default function ({renderProps, store}) {
3+
export default function ({renderProps, store, status}) {
44
return trigger('fetch', renderProps.components, {
55
params: renderProps.params,
66
dispatch: store.dispatch,
77
state: store.getState()
8-
}).then(() => Promise.resolve(({renderProps}))).catch(e => Promise.reject(e));
8+
}).then(() => Promise.resolve(({renderProps, status}))).catch(e => Promise.reject(e));
99
}

src/route-matcher.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
1+
import {OK, NOT_FOUND} from 'http-status-codes';
12
import {match, createMemoryHistory} from 'react-router';
23

4+
function determineStatusFrom(components) {
5+
if (components.map(component => component.displayName).includes('NotFound')) return NOT_FOUND;
6+
7+
return OK;
8+
}
9+
310
export default function matchRoute(url, routes) {
411
return new Promise((resolve, reject) => {
512
const history = createMemoryHistory();
@@ -9,7 +16,7 @@ export default function matchRoute(url, routes) {
916
reject(err);
1017
}
1118

12-
resolve({redirectLocation, renderProps});
19+
resolve({redirectLocation, renderProps, status: determineStatusFrom(renderProps.components)});
1320
});
1421
});
1522
}

src/router-wrapper.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@ import fetchData from './data-fetcher';
77

88
export default function renderThroughReactRouter(request, reply, {routes, respond, Root, store}) {
99
return matchRoute(request.raw.req.url, routes)
10-
.then(({renderProps}) => fetchData({renderProps, store}))
11-
.then(({renderProps}) => respond(reply, {
10+
.then(({renderProps, status}) => fetchData({renderProps, store, status}))
11+
.then(({renderProps, status}) => respond(reply, {
1212
store,
13+
status,
1314
renderedContent: renderToString(
1415
<Root request={request} store={store}>
1516
<RouterContext {...renderProps} />

test/unit/data-fetcher-test.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,11 @@ suite('data fetcher', () => {
2222
const state = any.simpleObject();
2323
const getState = sinon.stub().returns(state);
2424
const renderProps = {...any.simpleObject(), components, params};
25+
const status = any.integer();
2526
const store = {...any.simpleObject(), dispatch, getState};
2627
redial.trigger.withArgs('fetch', components, {params, dispatch, state}).resolves();
2728

28-
return assert.isFulfilled(fetchData({renderProps, store}), {renderProps});
29+
return assert.isFulfilled(fetchData({renderProps, store, status}), {renderProps, status});
2930
});
3031

3132
test('that a redial rejection bubbles', () => {

0 commit comments

Comments
 (0)