Skip to content

Commit 3fc8c6d

Browse files
committed
Fix server-rendering API
1 parent dfc69cb commit 3fc8c6d

File tree

5 files changed

+50
-62
lines changed

5 files changed

+50
-62
lines changed
Lines changed: 15 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,38 @@
11
# Server Rendering
22

3-
Server rendering is a bit different than in a client because you'll want
4-
to:
3+
Server rendering is a bit different than in a client because you'll want to:
54

65
- Send `500` responses for errors
76
- Send `30x` responses for redirects
87
- Fetch data before rendering (and use the router to help you do it)
98

10-
To facilitate these needs, you drop one level lower than the [`<Router/>`](/docs/API.md#Router)
11-
API with:
9+
To facilitate these needs, you drop one level lower than the [`<Router>`](/docs/API.md#Router) API with:
1210

13-
- `createMemoryHistory` from the history package
1411
- `match` to match the routes to a location without rendering
1512
- `RoutingContext` for synchronous rendering of route components
1613

1714
It looks something like this with an imaginary JavaScript server:
1815

1916
```js
20-
import createMemoryHistory from 'history/lib/createMemoryHistory'
21-
import { RoutingContext, match } from 'react-router'
22-
import routes from './routes'
2317
import { renderToString } from 'react-dom/server'
18+
import { match, RoutingContext } from 'react-router'
19+
import routes from './routes'
2420

2521
serve((req, res) => {
26-
const history = createMemoryHistory()
27-
const location = history.createLocation(req.url)
28-
29-
match({ routes, history, location }, (error, redirectLocation, renderProps) => {
30-
if (redirectLocation)
31-
res.redirect(301, redirectLocation.pathname + redirectLocation.search)
32-
else if (error)
22+
// Note that req.url here should be the full URL path from
23+
// the original request, including the query string.
24+
match(req.url, (error, redirectLocation, renderProps) => {
25+
if (redirectLocation) {
26+
res.redirect(302, redirectLocation.pathname + redirectLocation.search)
27+
} else if (error) {
3328
res.send(500, error.message)
34-
else if (renderProps == null)
29+
} else if (renderProps == null) {
3530
res.send(404, 'Not found')
36-
else
37-
res.send(renderToString(<RoutingContext {...renderProps}/>))
31+
} else {
32+
res.send(200, renderToString(<RoutingContext {...renderProps} />))
33+
}
3834
})
3935
})
4036
```
4137

42-
For data loading, you can use the `renderProps` argument to build whatever
43-
convention you want--like adding static `load` methods to your route
44-
components, or putting data loading functions on the routes, it's up to
45-
you.
38+
For data loading, you can use the `renderProps` argument to build whatever convention you want--like adding static `load` methods to your route components, or putting data loading functions on the routes--it's up to you.

modules/__tests__/serverRendering-test.js

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,8 @@
22
/*eslint react/prop-types: 0*/
33
import expect from 'expect'
44
import React from 'react'
5-
import createMemoryHistory from 'history/lib/createMemoryHistory'
6-
import RoutingContext from '../RoutingContext'
75
import match from '../match'
6+
import RoutingContext from '../RoutingContext'
87
import Link from '../Link'
98

109
describe('server rendering', function () {
@@ -71,9 +70,7 @@ describe('server rendering', function () {
7170
})
7271

7372
it('works', function (done) {
74-
const history = createMemoryHistory()
75-
const location = history.createLocation('/dashboard')
76-
match({ routes, history, location }, function (error, redirectLocation, renderProps) {
73+
match({ routes, location: '/dashboard' }, function (error, redirectLocation, renderProps) {
7774
const string = React.renderToString(
7875
<RoutingContext {...renderProps} />
7976
)
@@ -83,9 +80,7 @@ describe('server rendering', function () {
8380
})
8481

8582
it('renders active Links as active', function (done) {
86-
const history = createMemoryHistory()
87-
const location = history.createLocation('/about')
88-
match({ routes, history, location }, function (error, redirectLocation, renderProps) {
83+
match({ routes, location: '/about' }, function (error, redirectLocation, renderProps) {
8984
const string = React.renderToString(
9085
<RoutingContext {...renderProps} />
9186
)
@@ -96,9 +91,7 @@ describe('server rendering', function () {
9691
})
9792

9893
it('sends the redirect location', function (done) {
99-
const history = createMemoryHistory()
100-
const location = history.createLocation('/company')
101-
match({ routes, history, location }, function (error, redirectLocation) {
94+
match({ routes, location: '/company' }, function (error, redirectLocation) {
10295
expect(redirectLocation).toExist()
10396
expect(redirectLocation.pathname).toEqual('/about')
10497
expect(redirectLocation.search).toEqual('')
@@ -109,9 +102,7 @@ describe('server rendering', function () {
109102
})
110103

111104
it('sends null values when no routes match', function (done) {
112-
const history = createMemoryHistory()
113-
const location = history.createLocation('/no-match')
114-
match({ routes, history, location }, function (error, redirectLocation, state) {
105+
match({ routes, location: '/no-match' }, function (error, redirectLocation, state) {
115106
expect(error).toNotExist()
116107
expect(redirectLocation).toNotExist()
117108
expect(state).toNotExist()

modules/match.js

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,21 @@
1-
import createMemoryHistory from 'history/lib/createMemoryHistory'
2-
import useRoutes from './useRoutes'
1+
import createHistory from 'history/lib/createMemoryHistory'
32
import { createRoutes } from './RouteUtils'
3+
import useRoutes from './useRoutes'
44

55
function match({
66
routes,
7-
history,
87
location,
98
parseQueryString,
109
stringifyQuery
11-
}, cb) {
12-
let createHistory = history ? () => history : createMemoryHistory
13-
14-
let staticHistory = useRoutes(createHistory)({
10+
}, callback) {
11+
let history = useRoutes(createHistory)({
1512
routes: createRoutes(routes),
1613
parseQueryString,
1714
stringifyQuery
1815
})
1916

20-
staticHistory.match(location, function (error, nextLocation, nextState) {
21-
let renderProps = nextState ? { ...nextState, history: staticHistory } : null
22-
cb(error, nextLocation, renderProps)
17+
history.match(location, function (error, redirectLocation, nextState) {
18+
callback(error, redirectLocation, nextState && { ...nextState, history })
2319
})
2420
}
2521

modules/useRoutes.js

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,12 @@ function hasAnyProperties(object) {
1919
* Returns a new createHistory function that may be used to create
2020
* history objects that know about routing.
2121
*
22-
* - isActive(pathname, query)
23-
* - match(location, (error, nextState, nextLocation) => {})
24-
* - listenBeforeLeavingRoute(route, (?nextLocation) => {})
22+
* Enhances history objects with the following methods:
23+
*
2524
* - listen((error, nextState) => {})
25+
* - listenBeforeLeavingRoute(route, (nextLocation) => {})
26+
* - match(location, (error, redirectLocation, nextState) => {})
27+
* - isActive(pathname, query, indexOnly=false)
2628
*/
2729
function useRoutes(createHistory) {
2830
return function (options={}) {
@@ -34,13 +36,23 @@ function useRoutes(createHistory) {
3436
return _isActive(pathname, query, indexOnly, state.location, state.routes, state.params)
3537
}
3638

39+
function createLocationFromRedirectInfo({ pathname, query, state }) {
40+
return history.createLocation(
41+
history.createPath(pathname, query), state, REPLACE
42+
)
43+
}
44+
3745
let partialNextState
3846

3947
function match(location, callback) {
4048
if (partialNextState && partialNextState.location === location) {
4149
// Continue from where we left off.
4250
finishMatch(partialNextState, callback)
4351
} else {
52+
// Allow match(path)
53+
if (typeof location === 'string')
54+
location = history.createLocation(location)
55+
4456
matchRoutes(routes, location, function (error, nextState) {
4557
if (error) {
4658
callback(error)
@@ -53,12 +65,6 @@ function useRoutes(createHistory) {
5365
}
5466
}
5567

56-
function createLocationFromRedirectInfo({ pathname, query, state }) {
57-
return history.createLocation(
58-
history.createPath(pathname, query), state, REPLACE, history.createKey()
59-
)
60-
}
61-
6268
function finishMatch(nextState, callback) {
6369
let { leaveRoutes, enterRoutes } = computeChangedRoutes(state, nextState)
6470

@@ -75,7 +81,9 @@ function useRoutes(createHistory) {
7581
if (error) {
7682
callback(error)
7783
} else {
78-
callback(null, null, { ...nextState, components })
84+
// TODO: Make match a pure function and have some other API
85+
// for "match and update state".
86+
callback(null, null, (state = { ...nextState, components }))
7987
}
8088
})
8189
}
@@ -221,18 +229,18 @@ function useRoutes(createHistory) {
221229
if (state.location === location) {
222230
listener(null, state)
223231
} else {
224-
match(location, function (error, nextLocation, nextState) {
232+
match(location, function (error, redirectLocation, nextState) {
225233
if (error) {
226234
listener(error)
227-
} else if (nextLocation) {
228-
history.transitionTo(nextLocation)
235+
} else if (redirectLocation) {
236+
history.transitionTo(redirectLocation)
229237
} else if (nextState) {
230-
listener(null, (state = nextState))
238+
listener(null, nextState)
231239
} else {
232240
warning(
233241
false,
234242
'Location "%s" did not match any routes',
235-
location.pathname + location.search
243+
location.pathname + location.search + location.hash
236244
)
237245
}
238246
})

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
],
2626
"license": "MIT",
2727
"dependencies": {
28-
"history": "^1.12.0",
28+
"history": "1.12.1",
2929
"invariant": "^2.0.0",
3030
"warning": "^2.0.0"
3131
},

0 commit comments

Comments
 (0)