Skip to content

Commit

Permalink
Merge pull request #3556 from avanderhoorn/update-getChildRoutes-with…
Browse files Browse the repository at this point in the history
…-progressState

Make `params` available to getChildRoutes providers
  • Loading branch information
taion committed Jun 21, 2016
2 parents dbdd09b + 2aa9a9e commit c1167fb
Show file tree
Hide file tree
Showing 9 changed files with 84 additions and 35 deletions.
10 changes: 5 additions & 5 deletions docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ A function used to convert an object from [`<Link>`](#link)s or calls to
A function used to convert a query string into an object that gets passed to route component props.

##### `onError(error)`
While the router is matching, errors may bubble up, here is your opportunity to catch and deal with them. Typically these will come from async features like [`route.getComponents`](#getcomponentsnextstate-callback), [`route.getIndexRoute`](#getindexroutelocation-callback), and [`route.getChildRoutes`](#getchildrouteslocation-callback).
While the router is matching, errors may bubble up, here is your opportunity to catch and deal with them. Typically these will come from async features like [`route.getComponents`](#getcomponentsnextstate-callback), [`route.getIndexRoute`](#getindexroutelocation-callback), and [`route.getChildRoutes`](#getchildroutespartialnextstate-callback).

##### `onUpdate()`
Called whenever the router updates its state in response to URL changes.
Expand Down Expand Up @@ -368,8 +368,8 @@ A plain JavaScript object route definition. `<Router>` turns JSX `<Route>`s into
##### `childRoutes`
An array of child routes, same as `children` in JSX route configs.

##### `getChildRoutes(location, callback)`
Same as `childRoutes` but asynchronous and receives the `location`. Useful for code-splitting and dynamic route matching (given some state or session data to return a different set of child routes).
##### `getChildRoutes(partialNextState, callback)`
Same as `childRoutes` but asynchronous and receives the `partialNextState`. Useful for code-splitting and dynamic route matching (given some state or session data to return a different set of child routes).

###### `callback` signature
`cb(err, routesArray)`
Expand Down Expand Up @@ -399,8 +399,8 @@ let myRoute = {

let myRoute = {
path: 'picture/:id',
getChildRoutes(location, cb) {
let { state } = location
getChildRoutes(partialNextState, cb) {
let { state } = partialNextState

if (state && state.fromDashboard) {
cb(null, [dashboardPictureRoute])
Expand Down
4 changes: 2 additions & 2 deletions docs/guides/DynamicRouting.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ A router is the perfect place to handle code splitting: it's responsible for set

React Router does all of its [path matching](/docs/guides/RouteMatching.md) and component fetching asynchronously, which allows you to not only load up the components lazily, *but also lazily load the route configuration*. You really only need one route definition in your initial bundle, the router can resolve the rest on demand.

Routes may define [`getChildRoutes`](/docs/API.md#getchildrouteslocation-callback), [`getIndexRoute`](/docs/API.md#getindexroutelocation-callback), and [`getComponents`](/docs/API.md#getcomponentsnextstate-callback) methods. These are asynchronous and only called when needed. We call it "gradual matching". React Router will gradually match the URL and fetch only the amount of route configuration and components it needs to match the URL and render.
Routes may define [`getChildRoutes`](/docs/API.md#getchildroutespartialnextstate-callback), [`getIndexRoute`](/docs/API.md#getindexroutelocation-callback), and [`getComponents`](/docs/API.md#getcomponentsnextstate-callback) methods. These are asynchronous and only called when needed. We call it "gradual matching". React Router will gradually match the URL and fetch only the amount of route configuration and components it needs to match the URL and render.

Coupled with a smart code splitting tool like [webpack](http://webpack.github.io/), a once tiresome architecture is now simple and declarative.

```js
const CourseRoute = {
path: 'course/:courseId',

getChildRoutes(location, callback) {
getChildRoutes(partialNextState, callback) {
require.ensure([], function (require) {
callback(null, [
require('./routes/Announcements'),
Expand Down
2 changes: 1 addition & 1 deletion examples/huge-apps/routes/Course/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module.exports = {
path: 'course/:courseId',

getChildRoutes(location, cb) {
getChildRoutes(partialNextState, cb) {
require.ensure([], (require) => {
cb(null, [
require('./routes/Announcements'),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module.exports = {
path: 'announcements',

getChildRoutes(location, cb) {
getChildRoutes(partialNextState, cb) {
require.ensure([], (require) => {
cb(null, [
require('./routes/Announcement')
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module.exports = {
path: 'assignments',

getChildRoutes(location, cb) {
getChildRoutes(partialNextState, cb) {
require.ensure([], (require) => {
cb(null, [
require('./routes/Assignment')
Expand Down
29 changes: 26 additions & 3 deletions modules/__tests__/matchRoutes-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ describe('matchRoutes', function () {
if (childRoutes) {
delete route.childRoutes

route.getChildRoutes = function (location, callback) {
route.getChildRoutes = function (partialNextState, callback) {
setTimeout(function () {
callback(null, childRoutes)
})
Expand Down Expand Up @@ -291,10 +291,10 @@ describe('matchRoutes', function () {
})

describe('an asynchronous JSX route config', function () {
let getChildRoutes, getIndexRoute, jsxRoutes
let getChildRoutes, getIndexRoute, jsxRoutes, makeJsxNestedRoutes

beforeEach(function () {
getChildRoutes = function (location, callback) {
getChildRoutes = function (partialNextState, callback) {
setTimeout(function () {
callback(null, <Route path=":userID" />)
})
Expand All @@ -312,6 +312,19 @@ describe('matchRoutes', function () {
getChildRoutes={getChildRoutes}
getIndexRoute={getIndexRoute} />
])

makeJsxNestedRoutes = () => {
const spy = expect.spyOn({ getChildRoutes }, 'getChildRoutes').andCallThrough()
const routes = createRoutes([
<Route name="users"
path="users/:id">
<Route name="topic"
path=":topic"
getChildRoutes={spy} />
</Route>
])
return { spy, routes }
}
})

it('when getChildRoutes callback returns reactElements', function (done) {
Expand All @@ -330,6 +343,16 @@ describe('matchRoutes', function () {
done()
})
})

it('when getChildRoutes callback returns partialNextState', function (done) {
const jsxNestedRoutes = makeJsxNestedRoutes()
matchRoutes(jsxNestedRoutes.routes, createLocation('/users/5/details/others'), function () {
const state = jsxNestedRoutes.spy.calls[0].arguments[0]
expect(state).toExist()
expect(state.params).toEqual({ id: '5', topic: 'details' })
done()
})
})
})

it('complains about invalid index route with path', function (done) {
Expand Down
31 changes: 31 additions & 0 deletions modules/deprecateLocationProperties.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import warning from './routerWarning'
import { canUseMembrane } from './deprecateObjectProperties'

// No-op by default.
let deprecateLocationProperties = () => {}

if (__DEV__ && canUseMembrane) {
deprecateLocationProperties = (nextState, location) => {
const nextStateWithLocation = { ...nextState }

// I don't use deprecateObjectProperties here because I want to keep the
// same code path between development and production, in that we just
// assign extra properties to the copy of the state object in both cases.
for (const prop in location) {
if (!Object.prototype.hasOwnProperty.call(location, prop)) {
continue
}

Object.defineProperty(nextStateWithLocation, prop, {
get() {
warning(false, 'Accessing location properties from the first argument to `getComponent` and `getComponents` is deprecated. That argument is now the router state (`nextState`) rather than the location. To access the location, use `nextState.location`.')
return location[prop]
}
})
}

return nextStateWithLocation
}
}

export default deprecateLocationProperties
20 changes: 2 additions & 18 deletions modules/getComponents.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { mapAsync } from './AsyncUtils'
import { canUseMembrane } from './deprecateObjectProperties'
import warning from './routerWarning'
import deprecateLocationProperties from './deprecateLocationProperties'

function getComponentsForRoute(nextState, route, callback) {
if (route.component || route.components) {
Expand All @@ -18,23 +18,7 @@ function getComponentsForRoute(nextState, route, callback) {
let nextStateWithLocation

if (__DEV__ && canUseMembrane) {
nextStateWithLocation = { ...nextState }

// I don't use deprecateObjectProperties here because I want to keep the
// same code path between development and production, in that we just
// assign extra properties to the copy of the state object in both cases.
for (const prop in location) {
if (!Object.prototype.hasOwnProperty.call(location, prop)) {
continue
}

Object.defineProperty(nextStateWithLocation, prop, {
get() {
warning(false, 'Accessing location properties from the first argument to `getComponent` and `getComponents` is deprecated. That argument is now the router state (`nextState`) rather than the location. To access the location, use `nextState.location`.')
return location[prop]
}
})
}
nextStateWithLocation = deprecateLocationProperties(nextState, location)
} else {
nextStateWithLocation = { ...nextState, ...location }
}
Expand Down
19 changes: 15 additions & 4 deletions modules/matchRoutes.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,29 @@ import warning from './routerWarning'
import { loopAsync } from './AsyncUtils'
import { matchPattern } from './PatternUtils'
import { createRoutes } from './RouteUtils'
import { canUseMembrane } from './deprecateObjectProperties'
import deprecateLocationProperties from './deprecateLocationProperties'

function getChildRoutes(route, location, callback) {
function getChildRoutes(route, location, paramNames, paramValues, callback) {
if (route.childRoutes) {
return [ null, route.childRoutes ]
}
if (!route.getChildRoutes) {
return []
}

let sync = true, result
let sync = true, result, partialNextStateWithLocation
const partialNextState = {
params: createParams(paramNames, paramValues)
}

if (__DEV__ && canUseMembrane) {
partialNextStateWithLocation = deprecateLocationProperties(partialNextState, location)
} else {
partialNextStateWithLocation = { ...partialNextState, ...location }
}

route.getChildRoutes(location, function (error, childRoutes) {
route.getChildRoutes(partialNextStateWithLocation, function (error, childRoutes) {
childRoutes = !error && createRoutes(childRoutes)
if (sync) {
result = [ error, childRoutes ]
Expand Down Expand Up @@ -160,7 +171,7 @@ function matchRouteDeep(
}
}

const result = getChildRoutes(route, location, onChildRoutes)
const result = getChildRoutes(route, location, paramNames, paramValues, onChildRoutes)
if (result) {
onChildRoutes(...result)
}
Expand Down

0 comments on commit c1167fb

Please sign in to comment.